/*
|
* The contents of this file are subject to the terms of the Common Development and
|
* Distribution License (the License). You may not use this file except in compliance with the
|
* License.
|
*
|
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
|
* specific language governing permission and limitations under the License.
|
*
|
* When distributing Covered Software, include this CDDL Header Notice in each file and include
|
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
|
* Header, with the fields enclosed by brackets [] replaced by your own identifying
|
* information: "Portions copyright [year] [name of copyright owner]".
|
*
|
* Copyright 2026 3A Systems, LLC.
|
*/
|
package org.opends.quicksetup.installer;
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.opends.messages.ReplicationMessages.ERR_NO_REACHABLE_PEER_IN_THE_DOMAIN;
|
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.HashMap;
|
import java.util.HashSet;
|
import java.util.List;
|
import java.util.Set;
|
|
import org.forgerock.opendj.ldap.Entry;
|
import org.forgerock.opendj.ldap.LinkedAttribute;
|
import org.forgerock.opendj.ldap.LinkedHashMapEntry;
|
import org.opends.admin.ads.ADSContext;
|
import org.opends.admin.ads.ReplicaDescriptor;
|
import org.opends.admin.ads.ServerDescriptor;
|
import org.opends.server.DirectoryServerTestCase;
|
import org.testng.annotations.Test;
|
|
/**
|
* Tests the classification of the messages a task logs into its entry, as read by
|
* {@code dsreplication} and by the quick setup while they wait for a task to complete.
|
* <p>
|
* The messages below are the ones a real initialization task logs, in the form rendered by
|
* {@code org.opends.server.backends.task.Task#addLogMessage}: a start notice from the task
|
* scheduler, then whatever the task logged, then the completion notice the scheduler appends
|
* once the task is over - all of them before the terminal task state becomes visible.
|
*/
|
@SuppressWarnings("javadoc")
|
public class InstallerHelperTest extends DirectoryServerTestCase
|
{
|
private static final String TASK_STARTED =
|
"[09/Sep/2026:12:06:48 +0000] severity=\"NOTICE\" msgCount=0 msgID=org.opends.messages.backend-413"
|
+ " message=\"Initialize From Replica task quicksetup-initialize3 started execution\"";
|
private static final String PEERS_NOT_FOUND =
|
"[09/Sep/2026:12:08:48 +0000] severity=\"ERROR\" msgCount=1 msgID=org.opends.messages.replication-47"
|
+ " message=\"Domain dc=example,dc=com: the server with serverId=12345 is unreachable\"";
|
private static final String IMPORT_NOT_SUPPORTED =
|
"[09/Sep/2026:12:08:48 +0000] severity=\"ERROR\" msgCount=2 msgID=org.opends.messages.replication-82"
|
+ " message=\" Initialization cannot be done because import is not supported by the backend userRoot\"";
|
private static final String TASK_FINISHED =
|
"[09/Sep/2026:12:08:48 +0000] severity=\"NOTICE\" msgCount=3 msgID=org.opends.messages.backend-414"
|
+ " message=\"Initialize From Replica task quicksetup-initialize3 finished execution in the state"
|
+ " Stopped by error\"";
|
|
/**
|
* The message id of the peers not found error, as a task renders it. The predicate under test is
|
* built from the message descriptor: this pins the descriptor to the form the other tests use.
|
*/
|
@Test
|
public void peersNotFoundErrorIsIdentifiedByResourceNameAndOrdinal()
|
{
|
assertThat(ERR_NO_REACHABLE_PEER_IN_THE_DOMAIN.resourceName() + "-"
|
+ ERR_NO_REACHABLE_PEER_IN_THE_DOMAIN.ordinal()).isEqualTo("org.opends.messages.replication-47");
|
}
|
|
@Test
|
public void peersNotFoundErrorIsRecognized()
|
{
|
assertThat(new InstallerHelper().isPeersNotFoundError(PEERS_NOT_FOUND)).isTrue();
|
}
|
|
/**
|
* The ordinal alone identifies no message: it is unique within a message file only, and the
|
* count of the messages a task logged carries the same digits.
|
*/
|
@Test
|
public void anotherMessageCarryingTheOrdinalOfThePeersNotFoundErrorIsNotRecognized()
|
{
|
final String otherError =
|
"[09/Sep/2026:12:08:48 +0000] severity=\"ERROR\" msgCount=47 msgID=org.opends.messages.replication-45"
|
+ " message=\"On domain dc=example,dc=com, initialization of server with serverId:12345 has been"
|
+ " requested from a server with an invalid serverId:0. \"";
|
assertThat(new InstallerHelper().isPeersNotFoundError(otherError)).isFalse();
|
}
|
|
/**
|
* The ordinal is unique within a message file only: message 47 of another file is not this error.
|
* Nor is a message of the same file whose ordinal merely starts with the same digits.
|
*/
|
@Test
|
public void aMessageOfAnotherResourceOrWithALongerOrdinalIsNotRecognized()
|
{
|
final String waitingOnStartTime =
|
"[09/Sep/2026:12:08:48 +0000] severity=\"INFORMATION\" msgCount=1 msgID=org.opends.messages.task-47"
|
+ " message=\"Waiting on start time\"";
|
final String longerOrdinal =
|
"[09/Sep/2026:12:08:48 +0000] severity=\"ERROR\" msgCount=1 msgID=org.opends.messages.replication-470"
|
+ " message=\"Domain dc=example,dc=com: the server with serverId=12345 is unreachable\"";
|
assertThat(new InstallerHelper().isPeersNotFoundError(waitingOnStartTime)).isFalse();
|
assertThat(new InstallerHelper().isPeersNotFoundError(longerOrdinal)).isFalse();
|
}
|
|
@Test
|
public void aNoticeIsNotAPeersNotFoundError()
|
{
|
assertThat(new InstallerHelper().isPeersNotFoundError(TASK_STARTED)).isFalse();
|
assertThat(new InstallerHelper().isPeersNotFoundError((String) null)).isFalse();
|
}
|
|
/**
|
* The failure of a task is reported neither by the first message it logs nor by the last one, so
|
* all of them have to be tested. This is the sequence of the task of issue #995.
|
*/
|
@Test
|
public void peersNotFoundErrorIsFoundAmongAllTheMessagesOfTheTask()
|
{
|
final InstallerHelper helper = new InstallerHelper();
|
assertThat(helper.isPeersNotFoundError(Arrays.asList(TASK_STARTED, PEERS_NOT_FOUND, TASK_FINISHED))).isTrue();
|
assertThat(helper.isPeersNotFoundError(Arrays.asList(TASK_STARTED, IMPORT_NOT_SUPPORTED, TASK_FINISHED)))
|
.isFalse();
|
assertThat(helper.isPeersNotFoundError(Collections.<String> emptyList())).isFalse();
|
}
|
|
/** The completion notice appended by the task scheduler must not hide the cause of the failure. */
|
@Test
|
public void relevantLogMessageIsTheLastErrorRatherThanTheLastMessage()
|
{
|
assertThat(InstallerHelper.getRelevantLogMessage(
|
Arrays.asList(TASK_STARTED, PEERS_NOT_FOUND, TASK_FINISHED))).isEqualTo(PEERS_NOT_FOUND);
|
assertThat(InstallerHelper.getRelevantLogMessage(
|
Arrays.asList(TASK_STARTED, PEERS_NOT_FOUND, IMPORT_NOT_SUPPORTED, TASK_FINISHED)))
|
.isEqualTo(IMPORT_NOT_SUPPORTED);
|
}
|
|
/**
|
* The severity of a message is a field of its own, not a word of its text: a notice whose text
|
* names the state of a failed task is not the error.
|
*/
|
@Test
|
public void aNoticeMentioningAnErrorIsNotTheError()
|
{
|
final String notice =
|
"[09/Sep/2026:12:08:48 +0000] severity=\"NOTICE\" msgCount=3 msgID=org.opends.messages.backend-414"
|
+ " message=\"Initialize From Replica task quicksetup-initialize3 finished execution in the state"
|
+ " STOPPED_BY_ERROR\"";
|
assertThat(InstallerHelper.getRelevantLogMessage(Arrays.asList(TASK_STARTED, PEERS_NOT_FOUND, notice)))
|
.isEqualTo(PEERS_NOT_FOUND);
|
}
|
|
@Test
|
public void relevantLogMessageIsTheLastMessageWhenTheTaskLoggedNoError()
|
{
|
assertThat(InstallerHelper.getRelevantLogMessage(Arrays.asList(TASK_STARTED, TASK_FINISHED)))
|
.isEqualTo(TASK_FINISHED);
|
assertThat(InstallerHelper.getRelevantLogMessage(Collections.<String> emptyList())).isNull();
|
}
|
|
@Test
|
public void taskLogMessagesAreReadInTheOrderTheyWereLogged()
|
{
|
final Entry taskEntry = new LinkedHashMapEntry("ds-task-id=quicksetup-initialize3,cn=Scheduled Tasks,cn=Tasks");
|
taskEntry.addAttribute(new LinkedAttribute("ds-task-log-message",
|
TASK_STARTED, PEERS_NOT_FOUND, TASK_FINISHED));
|
|
final List<String> logMsgs = InstallerHelper.getTaskLogMessages(taskEntry);
|
|
assertThat(logMsgs).containsExactly(TASK_STARTED, PEERS_NOT_FOUND, TASK_FINISHED);
|
assertThat(new InstallerHelper().isPeersNotFoundError(logMsgs)).isTrue();
|
assertThat(InstallerHelper.getRelevantLogMessage(logMsgs)).isEqualTo(PEERS_NOT_FOUND);
|
}
|
|
@Test
|
public void taskWithoutLogMessagesReadsAsAnEmptyLog()
|
{
|
final Entry taskEntry = new LinkedHashMapEntry("ds-task-id=quicksetup-initialize3,cn=Scheduled Tasks,cn=Tasks");
|
|
assertThat(InstallerHelper.getTaskLogMessages(taskEntry)).isEmpty();
|
assertThat(InstallerHelper.getRelevantLogMessage(InstallerHelper.getTaskLogMessages(taskEntry))).isNull();
|
}
|
|
/**
|
* The server ids in use are the ones of the replication servers and of the replication domains of
|
* every base DN, in one set: a new replication server and a new replication domain both take their
|
* id outside of it. A replication server takes the total update messages sent to its own id as
|
* messages for itself, so a domain must not get the id of a replication server, nor a replication
|
* server the id of a domain (issue #1207).
|
*/
|
@Test
|
public void usedServerIdsAreTheIdsOfTheReplicationServersAndOfTheDomainsOfEveryBaseDN()
|
{
|
final ServerDescriptor combined = server(4242, replica(1), replica(2), notReplicatedReplica());
|
final ServerDescriptor directoryOnly = server(null, replica(3));
|
final ServerDescriptor replicationOnly = server(4243);
|
|
assertThat(InstallerHelper.getUsedServerIds(Arrays.asList(combined, directoryOnly, replicationOnly)))
|
.containsOnly(4242, 4243, 1, 2, 3);
|
}
|
|
/**
|
* A new id is taken outside of the used ids and added to them: the replication server and the
|
* domains that setup or enable create next cannot take it (issue #1207). Only 4242 is free.
|
*/
|
@Test
|
public void newReplicationIdIsTakenOutsideOfTheUsedIdsAndAddedToThem()
|
{
|
final Set<Integer> usedIds = new HashSet<>();
|
for (int id = 1; id < Short.MAX_VALUE; id++)
|
{
|
if (id != 4242)
|
{
|
usedIds.add(id);
|
}
|
}
|
|
assertThat(InstallerHelper.addNewReplicationId(usedIds)).isEqualTo(4242);
|
assertThat(usedIds).contains(4242).hasSize(Short.MAX_VALUE - 1);
|
}
|
|
private static ServerDescriptor server(Integer replicationServerId, ReplicaDescriptor... replicas)
|
{
|
final ServerDescriptor server =
|
ServerDescriptor.createStandalone(new HashMap<ADSContext.ServerProperty, Object>());
|
if (replicationServerId != null)
|
{
|
server.getServerProperties().put(ServerDescriptor.ServerProperty.IS_REPLICATION_SERVER, Boolean.TRUE);
|
server.getServerProperties().put(ServerDescriptor.ServerProperty.REPLICATION_SERVER_ID, replicationServerId);
|
}
|
server.setReplicas(new HashSet<>(Arrays.asList(replicas)));
|
return server;
|
}
|
|
private static ReplicaDescriptor replica(int serverId)
|
{
|
final ReplicaDescriptor replica = new ReplicaDescriptor();
|
replica.setServerId(serverId);
|
return replica;
|
}
|
|
/** A suffix of the server which is not replicated: its replica has no server id. */
|
private static ReplicaDescriptor notReplicatedReplica()
|
{
|
return new ReplicaDescriptor();
|
}
|
}
|