/* * 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.server.tasks; import static org.assertj.core.api.Assertions.assertThat; import static org.opends.messages.BackendMessages.NOTE_TASK_FINISHED; import static org.opends.messages.BackendMessages.NOTE_TASK_STARTED; import static org.opends.messages.ToolMessages.ERR_LDIFEXPORT_NO_BACKENDS_FOR_ID; import static org.opends.server.config.ConfigConstants.ATTR_TASK_LOG_MESSAGES; import static org.opends.server.protocols.internal.InternalClientConnection.getRootConnection; import static org.opends.server.protocols.internal.Requests.newSearchRequest; import java.util.ArrayList; import java.util.List; import java.util.UUID; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.ldap.DN; import org.forgerock.opendj.ldap.SearchScope; import org.opends.quicksetup.installer.InstallerHelper; import org.opends.server.TestCaseUtils; import org.opends.server.backends.task.TaskState; import org.opends.server.protocols.internal.InternalSearchOperation; import org.opends.server.types.Attribute; import org.opends.server.types.Entry; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; /** * Tests what the messages a task logs into its entry look like once the task is over, since the * quick setup and {@code dsreplication} read them to report and to classify the failure of the * tasks they run - see {@code InstallerHelper} and issue #995. */ @SuppressWarnings("javadoc") public class TaskLogMessagesTestCase extends TasksTestCase { @BeforeClass public void startServer() throws Exception { TestCaseUtils.startServer(); } /** * The failure of a task is reported by neither the first nor the last message it logged: the * task scheduler frames the execution with a start and a completion notice, and both of them are * in the entry before the terminal task state is, so a reader that waits for that state always * sees them. The cause has to be looked up among all the messages. */ @Test public void failureOfATaskIsReportedBetweenTheNoticesOfTheScheduler() throws Exception { final String taskDN = "ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks"; // Exporting an unknown backend fails the task without touching any data. final Entry taskEntry = TestCaseUtils.makeEntry( "dn: " + taskDN, "objectclass: top", "objectclass: ds-task", "objectclass: ds-task-export", "ds-task-class-name: org.opends.server.tasks.ExportTask", "ds-task-export-backend-id: no-such-backend", "ds-task-export-ldif-file: " + TestCaseUtils.createTempFile()); testTask(taskEntry, TaskState.STOPPED_BY_ERROR, 60); final List logMsgs = getLogMessages(DN.valueOf(taskDN)); assertThat(logMsgs.size()).isGreaterThanOrEqualTo(3); assertThat(logMsgs.get(0)).contains(msgIdField(NOTE_TASK_STARTED.resourceName(), NOTE_TASK_STARTED.ordinal())); assertThat(logMsgs.get(logMsgs.size() - 1)) .contains(msgIdField(NOTE_TASK_FINISHED.resourceName(), NOTE_TASK_FINISHED.ordinal())); final String failure = InstallerHelper.getRelevantLogMessage(logMsgs); assertThat(failure).contains("severity=\"ERROR\"") .contains(msgIdField(ERR_LDIFEXPORT_NO_BACKENDS_FOR_ID.resourceName(), ERR_LDIFEXPORT_NO_BACKENDS_FOR_ID.ordinal())); assertThat(logMsgs.indexOf(failure)).isGreaterThan(0).isLessThan(logMsgs.size() - 1); assertThat(new InstallerHelper().isPeersNotFoundError(logMsgs)).isFalse(); } /** * The id a message is logged with, in the form rendered by * {@code org.opends.server.backends.task.Task#addLogMessage}. This is what the classification of * a task failure matches on, so the two must agree. */ private String msgIdField(String resourceName, int ordinal) { return "msgID=" + resourceName + "-" + ordinal; } private List getLogMessages(DN taskDN) { final InternalSearchOperation searchOperation = getRootConnection().processSearch(newSearchRequest(taskDN, SearchScope.BASE_OBJECT)); final Entry taskEntry = searchOperation.getSearchEntries().getFirst(); final List logMsgs = new ArrayList<>(); for (Attribute attribute : taskEntry.getAllAttributes(ATTR_TASK_LOG_MESSAGES)) { for (ByteString value : attribute) { logMsgs.add(value.toString()); } } return logMsgs; } }