-
Notifications
You must be signed in to change notification settings - Fork 214
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Issue #106: Reworked handling of matching validation result failure.
* Only send 'AddConnectionLogEntry' to Connectivity shard if the live response came from Connectivity. Otherwise, log warning that connection ID of sender is unknown. * Set detail message of response validation failure as description for timeout exception in case no valid live response arrived within the command's specified or default timeout. * Extracted factory for creating a 'LogEntry' for failed command-response-round-trips to make it re-usable. Signed-off-by: Juergen Fickel <juergen.fickel@bosch.io>
- Loading branch information
Juergen Fickel
committed
Nov 12, 2021
1 parent
9625bb7
commit 878f960
Showing
6 changed files
with
515 additions
and
72 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
...in/java/org/eclipse/ditto/connectivity/api/messaging/monitoring/logs/LogEntryFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/* | ||
* Copyright (c) 2021 Contributors to the Eclipse Foundation | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information regarding copyright ownership. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.eclipse.ditto.connectivity.api.messaging.monitoring.logs; | ||
|
||
import static org.eclipse.ditto.internal.models.signal.SignalInformationPoint.getCorrelationId; | ||
import static org.eclipse.ditto.internal.models.signal.SignalInformationPoint.getEntityId; | ||
|
||
import java.time.Instant; | ||
import java.util.function.Predicate; | ||
|
||
import javax.annotation.concurrent.Immutable; | ||
|
||
import org.eclipse.ditto.base.model.common.ConditionChecker; | ||
import org.eclipse.ditto.base.model.signals.commands.Command; | ||
import org.eclipse.ditto.base.model.signals.commands.CommandResponse; | ||
import org.eclipse.ditto.connectivity.model.ConnectivityModelFactory; | ||
import org.eclipse.ditto.connectivity.model.LogCategory; | ||
import org.eclipse.ditto.connectivity.model.LogEntry; | ||
import org.eclipse.ditto.connectivity.model.LogLevel; | ||
import org.eclipse.ditto.connectivity.model.LogType; | ||
|
||
/** | ||
* Factory for creating instances of {@link LogEntry}. | ||
* | ||
* @since 2.2.0 | ||
*/ | ||
@Immutable | ||
public final class LogEntryFactory { | ||
|
||
private LogEntryFactory() { | ||
throw new AssertionError(); | ||
} | ||
|
||
/** | ||
* Returns a {@code LogEntry} for a failed round-trip of the specified {@code Command} and {@code CommandResponse}. | ||
* The failure is described by the specified detail message string argument. | ||
* | ||
* @param command the command of the round-trip. | ||
* @param commandResponse the response of the round-trip. | ||
* @param detailMessage describes the reason for the failed round-trip. | ||
* @throws NullPointerException if any argument is {@code null}. | ||
* @throws IllegalArgumentException if {@code detailMessage} is blank. | ||
*/ | ||
public static LogEntry getLogEntryForFailedCommandResponseRoundTrip(final Command<?> command, | ||
final CommandResponse<?> commandResponse, | ||
final String detailMessage) { | ||
|
||
ConditionChecker.checkNotNull(command, "command"); | ||
ConditionChecker.checkNotNull(commandResponse, "commandResponse"); | ||
ConditionChecker.checkArgument(ConditionChecker.checkNotNull(detailMessage, "detailMessage"), | ||
Predicate.not(String::isBlank), | ||
() -> "The detailMessage must not be blank."); | ||
|
||
final var logEntryBuilder = ConnectivityModelFactory.newLogEntryBuilder( | ||
getCorrelationId(command).or(() -> getCorrelationId(commandResponse)).orElse("n/a"), | ||
Instant.now(), | ||
LogCategory.RESPONSE, | ||
LogType.DROPPED, | ||
LogLevel.FAILURE, | ||
detailMessage | ||
); | ||
|
||
getEntityId(command).or(() -> getEntityId(commandResponse)).ifPresent(logEntryBuilder::entityId); | ||
|
||
return logEntryBuilder.build(); | ||
} | ||
|
||
} |
237 changes: 237 additions & 0 deletions
237
...ava/org/eclipse/ditto/connectivity/api/messaging/monitoring/logs/LogEntryFactoryTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
/* | ||
* Copyright (c) 2021 Contributors to the Eclipse Foundation | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information regarding copyright ownership. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
*/ | ||
package org.eclipse.ditto.connectivity.api.messaging.monitoring.logs; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; | ||
import static org.assertj.core.api.Assertions.assertThatNullPointerException; | ||
import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf; | ||
import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable; | ||
|
||
import java.time.Instant; | ||
|
||
import org.eclipse.ditto.base.model.correlationid.TestNameCorrelationId; | ||
import org.eclipse.ditto.base.model.entity.id.EntityId; | ||
import org.eclipse.ditto.base.model.entity.id.WithEntityId; | ||
import org.eclipse.ditto.base.model.headers.DittoHeaders; | ||
import org.eclipse.ditto.base.model.signals.commands.Command; | ||
import org.eclipse.ditto.base.model.signals.commands.CommandResponse; | ||
import org.eclipse.ditto.connectivity.model.LogCategory; | ||
import org.eclipse.ditto.connectivity.model.LogLevel; | ||
import org.eclipse.ditto.connectivity.model.LogType; | ||
import org.junit.Before; | ||
import org.junit.Rule; | ||
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
import org.mockito.Mock; | ||
import org.mockito.Mockito; | ||
import org.mockito.junit.MockitoJUnitRunner; | ||
|
||
/** | ||
* Unit test for {@link LogEntryFactory}. | ||
*/ | ||
@RunWith(MockitoJUnitRunner.class) | ||
public final class LogEntryFactoryTest { | ||
|
||
private static final String DETAIL_MESSAGE_FAILURE = "This is the failure detail message."; | ||
|
||
@Rule | ||
public final TestNameCorrelationId testNameCorrelationId = TestNameCorrelationId.newInstance(); | ||
|
||
@Mock | ||
private Command<?> command; | ||
|
||
@Mock | ||
private CommandResponse<?> commandResponse; | ||
|
||
private DittoHeaders dittoHeadersWithCorrelationId; | ||
|
||
@Before | ||
public void before() { | ||
dittoHeadersWithCorrelationId = | ||
DittoHeaders.newBuilder().correlationId(testNameCorrelationId.getCorrelationId()).build(); | ||
|
||
final var emptyDittoHeaders = DittoHeaders.empty(); | ||
Mockito.when(command.getDittoHeaders()).thenReturn(emptyDittoHeaders); | ||
Mockito.when(commandResponse.getDittoHeaders()).thenReturn(emptyDittoHeaders); | ||
} | ||
|
||
@Test | ||
public void assertImmutability() { | ||
assertInstancesOf(LogEntryFactory.class, areImmutable()); | ||
} | ||
|
||
@Test | ||
public void getLogEntryForFailedCommandResponseRoundTripWithNullCommandThrowsException() { | ||
assertThatNullPointerException() | ||
.isThrownBy(() -> LogEntryFactory.getLogEntryForFailedCommandResponseRoundTrip(null, | ||
commandResponse, | ||
DETAIL_MESSAGE_FAILURE)) | ||
.withMessage("The command must not be null!") | ||
.withNoCause(); | ||
} | ||
|
||
@Test | ||
public void getLogEntryForFailedCommandResponseRoundTripWithNullCommandResponseThrowsException() { | ||
assertThatNullPointerException() | ||
.isThrownBy(() -> LogEntryFactory.getLogEntryForFailedCommandResponseRoundTrip(command, | ||
null, | ||
DETAIL_MESSAGE_FAILURE)) | ||
.withMessage("The commandResponse must not be null!") | ||
.withNoCause(); | ||
} | ||
|
||
@Test | ||
public void getLogEntryForFailedCommandResponseRoundTripWithNullDetailMessageThrowsException() { | ||
assertThatNullPointerException() | ||
.isThrownBy(() -> LogEntryFactory.getLogEntryForFailedCommandResponseRoundTrip(command, | ||
commandResponse, | ||
null)) | ||
.withMessage("The detailMessage must not be null!") | ||
.withNoCause(); | ||
} | ||
|
||
@Test | ||
public void getLogEntryForFailedCommandResponseRoundTripWithBlankDetailMessageThrowsException() { | ||
assertThatIllegalArgumentException() | ||
.isThrownBy(() -> LogEntryFactory.getLogEntryForFailedCommandResponseRoundTrip(command, | ||
commandResponse, | ||
" ")) | ||
.withMessage("The detailMessage must not be blank.") | ||
.withNoCause(); | ||
} | ||
|
||
@Test | ||
public void getLogEntryForFailedCommandResponseRoundTripWithNoCorrelationIdReturnsExpected() { | ||
final var emptyDittoHeaders = DittoHeaders.empty(); | ||
Mockito.when(command.getDittoHeaders()).thenReturn(emptyDittoHeaders); | ||
Mockito.when(commandResponse.getDittoHeaders()).thenReturn(emptyDittoHeaders); | ||
|
||
final var logEntry = LogEntryFactory.getLogEntryForFailedCommandResponseRoundTrip(command, | ||
commandResponse, | ||
DETAIL_MESSAGE_FAILURE); | ||
|
||
assertThat(logEntry.getCorrelationId()).isEqualTo("n/a"); | ||
} | ||
|
||
@Test | ||
public void getLogEntryForFailedCommandResponseRoundTripWithCommandCorrelationIdOnlyReturnsExpected() { | ||
Mockito.when(command.getDittoHeaders()).thenReturn(dittoHeadersWithCorrelationId); | ||
|
||
final var logEntry = LogEntryFactory.getLogEntryForFailedCommandResponseRoundTrip(command, | ||
commandResponse, | ||
DETAIL_MESSAGE_FAILURE); | ||
|
||
assertThat(logEntry.getCorrelationId()).isEqualTo(testNameCorrelationId.getCorrelationId().toString()); | ||
} | ||
|
||
@Test | ||
public void getLogEntryForFailedCommandResponseRoundTripWithPrefersCommandCorrelationIdOnlyReturnsExpected() { | ||
Mockito.when(command.getDittoHeaders()).thenReturn(dittoHeadersWithCorrelationId); | ||
Mockito.lenient() | ||
.when(commandResponse.getDittoHeaders()) | ||
.thenReturn(DittoHeaders.newBuilder().correlationId("anotherCorrelationId").build()); | ||
|
||
final var logEntry = LogEntryFactory.getLogEntryForFailedCommandResponseRoundTrip(command, | ||
commandResponse, | ||
DETAIL_MESSAGE_FAILURE); | ||
|
||
assertThat(logEntry.getCorrelationId()).isEqualTo(testNameCorrelationId.getCorrelationId().toString()); | ||
} | ||
|
||
@Test | ||
public void getLogEntryForFailedCommandResponseRoundTripReturnsLogEntryWithTimestampCloseToNow() { | ||
final var logEntry = LogEntryFactory.getLogEntryForFailedCommandResponseRoundTrip(command, | ||
commandResponse, | ||
DETAIL_MESSAGE_FAILURE); | ||
final var instantNow = Instant.now(); | ||
|
||
assertThat(logEntry.getTimestamp()).isBetween(instantNow.minusMillis(500L), instantNow); | ||
} | ||
|
||
@Test | ||
public void getLogEntryForFailedCommandResponseRoundTripReturnsLogEntryWithLogCategoryResponse() { | ||
final var logEntry = LogEntryFactory.getLogEntryForFailedCommandResponseRoundTrip(command, | ||
commandResponse, | ||
DETAIL_MESSAGE_FAILURE); | ||
|
||
assertThat(logEntry.getLogCategory()).isEqualTo(LogCategory.RESPONSE); | ||
} | ||
|
||
@Test | ||
public void getLogEntryForFailedCommandResponseRoundTripReturnsLogEntryWithLogTypeDropped() { | ||
final var logEntry = LogEntryFactory.getLogEntryForFailedCommandResponseRoundTrip(command, | ||
commandResponse, | ||
DETAIL_MESSAGE_FAILURE); | ||
|
||
assertThat(logEntry.getLogType()).isEqualTo(LogType.DROPPED); | ||
} | ||
|
||
@Test | ||
public void getLogEntryForFailedCommandResponseRoundTripReturnsLogEntryWithLogLevelFailure() { | ||
final var logEntry = LogEntryFactory.getLogEntryForFailedCommandResponseRoundTrip(command, | ||
commandResponse, | ||
DETAIL_MESSAGE_FAILURE); | ||
|
||
assertThat(logEntry.getLogLevel()).isEqualTo(LogLevel.FAILURE); | ||
} | ||
|
||
@Test | ||
public void getLogEntryForFailedCommandResponseRoundTripReturnsLogEntryWithExpectedDetailMessage() { | ||
final var logEntry = LogEntryFactory.getLogEntryForFailedCommandResponseRoundTrip(command, | ||
commandResponse, | ||
DETAIL_MESSAGE_FAILURE); | ||
|
||
assertThat(logEntry.getMessage()).isEqualTo(DETAIL_MESSAGE_FAILURE); | ||
} | ||
|
||
@Test | ||
public void getLogEntryForFailedCommandResponseRoundTripReturnsLogEntryWithoutEntityId() { | ||
final var logEntry = LogEntryFactory.getLogEntryForFailedCommandResponseRoundTrip(command, | ||
commandResponse, | ||
DETAIL_MESSAGE_FAILURE); | ||
|
||
assertThat(logEntry.getEntityId()).isEmpty(); | ||
} | ||
|
||
@Test | ||
public void getLogEntryForFailedCommandResponseRoundTripReturnsLogEntryWithEntityIdOfCommand() { | ||
final var entityId = Mockito.mock(EntityId.class); | ||
final Command<?> commandWithEntityId = | ||
Mockito.mock(Command.class, Mockito.withSettings().extraInterfaces(WithEntityId.class)); | ||
Mockito.when(commandWithEntityId.getDittoHeaders()).thenReturn(dittoHeadersWithCorrelationId); | ||
Mockito.when(((WithEntityId) commandWithEntityId).getEntityId()).thenReturn(entityId); | ||
|
||
final var logEntry = LogEntryFactory.getLogEntryForFailedCommandResponseRoundTrip(commandWithEntityId, | ||
commandResponse, | ||
DETAIL_MESSAGE_FAILURE); | ||
|
||
assertThat(logEntry.getEntityId()).hasValue(entityId); | ||
} | ||
|
||
@Test | ||
public void getLogEntryForFailedCommandResponseRoundTripReturnsLogEntryWithEntityIdOfCommandResponse() { | ||
final var entityId = Mockito.mock(EntityId.class); | ||
final CommandResponse<?> commandResponseWithEntityId = | ||
Mockito.mock(CommandResponse.class, Mockito.withSettings().extraInterfaces(WithEntityId.class)); | ||
Mockito.when(commandResponseWithEntityId.getDittoHeaders()).thenReturn(dittoHeadersWithCorrelationId); | ||
Mockito.when(((WithEntityId) commandResponseWithEntityId).getEntityId()).thenReturn(entityId); | ||
|
||
final var logEntry = LogEntryFactory.getLogEntryForFailedCommandResponseRoundTrip(command, | ||
commandResponseWithEntityId, | ||
DETAIL_MESSAGE_FAILURE); | ||
|
||
assertThat(logEntry.getEntityId()).hasValue(entityId); | ||
} | ||
|
||
} |
Oops, something went wrong.