Skip to content

Commit

Permalink
Add backend support for errors during simulations
Browse files Browse the repository at this point in the history
1. The "result" and "resultStatus" were added to ProcessedObject and
SimulationResultProcessedObjectType.

2. Built-in metric "errors" was added.

Related change:

- We had to add Clockwork.runWithConflictDetection operation result
between Clockwork.run and Clockwork.click, in order to provide closed
OperationResult objects to the simulation data writer.

Related to MID-8622.
  • Loading branch information
mederly committed Mar 22, 2023
1 parent 253523b commit 3619ed4
Show file tree
Hide file tree
Showing 16 changed files with 351 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ public static Map<BuiltInSimulationMetricType, Integer> getBuiltInMetrics(Simula
BuiltInSimulationMetricType identifier = metric.getRef().getBuiltIn();

BigDecimal value = SimulationMetricValuesTypeUtil.getValue(metric);
map.put(identifier, value != null ? value.intValue() : 0);
map.put(identifier, value.intValue());
}

return map;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1210,6 +1210,13 @@ public void close() {
computeStatusIfUnknown();
}

public boolean isClosed() {
return status != OperationResultStatus.UNKNOWN
&& status != null
&& end != null
&& invocationRecord == null;
}

public void computeStatusIfUnknown() {
recordEnd();
if (isUnknown()) {
Expand Down Expand Up @@ -2005,21 +2012,27 @@ public static OperationResult createOperationResult(OperationResultType bean) {
* the size of e.g. {@link ShadowType} objects with fetchResult that includes full traced clockwork processing.
*/
public OperationResultType createBeanReduced() {
return createOperationResultBean(this, null, true);
return createOperationResultBean(this, null, BeanContent.REDUCED);
}
/**
* As {@link #createOperationResultType()} but exports only the root result.
*/
public OperationResultType createBeanRootOnly() {
return createOperationResultBean(this, null, BeanContent.ROOT_ONLY);
}

public @NotNull OperationResultType createOperationResultType() {
return createOperationResultType(null);
}

public @NotNull OperationResultType createOperationResultType(Function<LocalizableMessage, String> resolveKeys) {
return createOperationResultBean(this, resolveKeys, false);
return createOperationResultBean(this, resolveKeys, BeanContent.FULL);
}

private static @NotNull OperationResultType createOperationResultBean(
@NotNull OperationResult opResult,
Function<LocalizableMessage, String> resolveKeys,
boolean reduce) {
@Nullable Function<LocalizableMessage, String> resolveKeys,
@NotNull BeanContent beanContent) {
OperationResultType bean = new OperationResultType();
bean.setOperationKind(opResult.getOperationKind());
bean.setToken(opResult.getToken());
Expand Down Expand Up @@ -2075,12 +2088,14 @@ public OperationResultType createBeanReduced() {
bean.setContext(opResult.getContextBean());
bean.setReturns(opResult.getReturnsBean());

for (OperationResult subResult : opResult.getSubresults()) {
if (reduce && subResult.isMinor() && subResult.isSuccess()) {
continue;
if (beanContent != BeanContent.ROOT_ONLY) {
for (OperationResult subResult : opResult.getSubresults()) {
if (beanContent == BeanContent.REDUCED && subResult.isMinor() && subResult.isSuccess()) {
continue;
}
bean.getPartialResults().add(
createOperationResultBean(subResult, resolveKeys, beanContent));
}
bean.getPartialResults().add(
createOperationResultBean(subResult, resolveKeys, reduce));
}

bean.setAsynchronousOperationReference(opResult.getAsynchronousOperationReference());
Expand Down Expand Up @@ -2816,4 +2831,17 @@ public TraceDictionaryType getExtractedDictionary() {
public void setExtractedDictionary(TraceDictionaryType extractedDictionary) {
this.extractedDictionary = extractedDictionary;
}

/** What should be serialized into {@link OperationResultType} bean? */
private enum BeanContent {

/** Everything. */
FULL,

/** Everything except for minor success children. */
REDUCED,

/** No children. */
ROOT_ONLY
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,16 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="errors">
<xsd:annotation>
<xsd:documentation>
Number of objects whose processing finished with a partial or fatal error.
</xsd:documentation>
<xsd:appinfo>
<jaxb:typesafeEnumMember name="ERRORS"/>
</xsd:appinfo>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>

Expand Down Expand Up @@ -1154,6 +1164,32 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="result" type="tns:OperationResultType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Result of the whole operation.

It is present on "primary" processed objects, for example focus objects for recomputation activity,
or shadows from the source resource for synchronization (import, reconciliation, etc) activities.

Note that it is no point in providing result or result status on "target" objects on which simulated
changes are computed. Because these changes are not executed, they have no real result nor result status.
Hence, we provide only a single result per operation, and provide it on the "primary" processed object.

If the result is missing, SUCCESS is assumed.

The result may be shortened, i.e. some parts may be removed to save space.
(Currently, only the root level is kept.)
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="resultStatus" type="tns:OperationResultStatusType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Status of the operation. Present on "primary" processed objects. See the "result" property.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="simulationResultProcessedObject" type="tns:SimulationResultProcessedObjectType"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.result.OperationResultStatus;
import com.evolveum.midpoint.schema.util.delta.ItemDeltaFilter;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.DebugDumpable;
Expand Down Expand Up @@ -76,6 +77,24 @@ public interface ProcessedObject<O extends ObjectType> extends DebugDumpable, Se
*/
@NotNull ObjectProcessingStateType getState();

/**
* The status of the operation connected to this object.
*
* Only "primary" objects have this information.
*
* @see SimulationResultProcessedObjectType#getResultStatus()
*/
@Nullable OperationResultStatus getResultStatus();

/**
* The result of the operation connected to this object.
*
* Only "primary" objects have this information.
*
* @see SimulationResultProcessedObjectType#getResult()
*/
@Nullable OperationResult getResult();

/** Returns OIDs of matching event marks for this object. */
@NotNull Collection<String> getMatchingEventMarks();

Expand Down Expand Up @@ -104,6 +123,11 @@ default O getAfterOrBefore() {
return MiscUtil.getFirstNonNull(getAfter(), getBefore());
}

/**
* Creates a {@link SimulationResultProcessedObjectType} bean corresponding to this object.
*/
@NotNull SimulationResultProcessedObjectType toBean();

/** For diagnostic purposes. */
@VisibleForTesting
void resolveEventMarks(OperationResult result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public class Clockwork {
private static final Trace LOGGER = TraceManager.getTrace(Clockwork.class);

private static final String OP_RUN = Clockwork.class.getName() + ".run";
private static final String OP_RUN_WITH_CONFLICT_DETECTION = Clockwork.class.getName() + ".runWithConflictDetection";
private static final String OP_WRITE_SIMULATION_DATA = Clockwork.class.getName() + ".writeSimulationData";

@Autowired private Projector projector;
Expand Down Expand Up @@ -125,19 +126,20 @@ public <F extends ObjectType> HookOperationMode run(LensContext<F> context, Task
* It reports such states via the conflictResolutionContext parameter.
*/
<F extends ObjectType> HookOperationMode runWithConflictDetection(LensContext<F> context,
ClockworkConflictResolver.Context conflictResolutionContext, Task task, OperationResult result)
ClockworkConflictResolver.Context conflictResolutionContext, Task task, OperationResult parentResult)
throws SchemaException, PolicyViolationException, ExpressionEvaluationException, ObjectNotFoundException,
ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException {

context.setStartedIfNotYet();
context.updateSystemConfiguration(result);
OperationResult result = parentResult.createSubresult(OP_RUN_WITH_CONFLICT_DETECTION);
try {
context.setStartedIfNotYet();
context.updateSystemConfiguration(result);

LOGGER.trace("Running clockwork for context {}", context);
context.checkConsistenceIfNeeded();
LOGGER.trace("Running clockwork for context {}", context);
context.checkConsistenceIfNeeded();

context.resetClickCounter();
context.resetClickCounter();

try {
context.reportProgress(new ProgressInformation(CLOCKWORK, ENTERING));
clockworkConflictResolver.createConflictWatcherOnStart(context);
enterCaches();
Expand Down Expand Up @@ -171,19 +173,25 @@ <F extends ObjectType> HookOperationMode runWithConflictDetection(LensContext<F>
}
} finally {
operationExecutionRecorder.recordOperationExecutions(context, task, result);
writeFullSimulationData(context, task, result);
clockworkConflictResolver.unregisterConflictWatcher(context);
exitCaches();
context.reportProgress(new ProgressInformation(CLOCKWORK, EXITING));

result.close();
writeFullSimulationData(context, task, result, parentResult);
}
}

private void writeFullSimulationData(LensContext<?> context, Task task, OperationResult parentResult) {
private void writeFullSimulationData(
LensContext<?> context, Task task, OperationResult resultToRecord, OperationResult parentResult) {
assert resultToRecord.isClosed();
SimulationTransaction transactionContext = task.getSimulationTransaction();
if (!task.isExecutionFullyPersistent() && transactionContext != null) {
OperationResult result = parentResult.createMinorSubresult(OP_WRITE_SIMULATION_DATA);
try {
transactionContext.writeSimulationData(FullOperationSimulationDataImpl.with(context), task, result);
transactionContext.writeSimulationData(
FullOperationSimulationDataImpl.with(context, resultToRecord),
task, result);
} catch (Throwable t) {
result.recordException(t);
throw t;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ private <F extends ObjectType> HookOperationMode resolveFocusConflict(LensContex
ClockworkConflictResolver.Context conflictResolutionContext = new ClockworkConflictResolver.Context();

// this is a recursion; but limited to max attempts which should not be a large number
HookOperationMode hookOperationMode = clockwork.runWithConflictDetection(contextNew, conflictResolutionContext, task, result);
HookOperationMode hookOperationMode =
clockwork.runWithConflictDetection(contextNew, conflictResolutionContext, task, result);

if (!conflictResolutionContext.focusConflictPresent) {
LOGGER.debug("CONFLICT: Clean recompute of {} achieved (options={}, attempts={},{})",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ public class LensProjectionContext extends LensElementContext<ShadowType> implem
* Is this projection the source of the synchronization? (The syncDelta attribute could be used for this but in
* reality it is not always present.)
*
* This information was once used for operation execution recording, but is not used now.
* This information was once used for operation execution recording. It is no longer the case.
* But since 4.7 we use it for recording result and result status during simulations.
*/
private boolean synchronizationSource;

Expand Down Expand Up @@ -1734,6 +1735,10 @@ public void setSynchronizationSource(boolean synchronizationSource) {
this.synchronizationSource = synchronizationSource;
}

public boolean isSynchronizationSource() {
return synchronizationSource;
}

public String getDescription() {
if (resource != null) {
return resource + "("+ key.getIntent()+")";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ class OperationExecutionRecorderForClockwork {
@Autowired private PrismContext prismContext;
@Autowired private OperationExecutionWriter writer;

private static final String OP_RECORD_OPERATION_EXECUTIONS = OperationExecutionRecorderForClockwork.class.getName() + ".recordOperationExecutions";
private static final String OP_RECORD_OPERATION_EXECUTIONS =
OperationExecutionRecorderForClockwork.class.getName() + ".recordOperationExecutions";

<F extends ObjectType> void recordOperationExecutions(LensContext<F> lensContext, Task task, OperationResult parentResult) {

Expand Down Expand Up @@ -154,8 +155,8 @@ private <F extends ObjectType> void writeFocusDeltas(Context<F> ctx, OperationRe

OperationExecutionType recordToAdd = createExecutionRecord(ctx.focusDeltas, ctx);
Class<F> focusType = ctx.getFocusContext().getObjectTypeClass();
OperationExecutionWriter.Request<F> request = new OperationExecutionWriter.Request<>(focusType, focusOid,
recordToAdd, ctx.getExistingExecutions(focusOid), ctx.isDeletedOk(focusOid));
OperationExecutionWriter.Request<F> request = new OperationExecutionWriter.Request<>(
focusType, focusOid, recordToAdd, ctx.getExistingExecutions(focusOid), ctx.isDeletedOk(focusOid));
try {
writer.write(request, result);
} catch (Throwable t) {
Expand Down Expand Up @@ -346,5 +347,4 @@ private boolean isDeletedOk(String oid) {
return deletedObjects.contains(oid);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

package com.evolveum.midpoint.model.impl.simulation;

import com.evolveum.midpoint.schema.result.OperationResult;

import org.jetbrains.annotations.NotNull;

import com.evolveum.midpoint.model.impl.lens.LensContext;
Expand All @@ -18,16 +20,24 @@
public class FullOperationSimulationDataImpl implements SimulationData {

@NotNull private final LensContext<?> lensContext;
@NotNull private final OperationResult resultToRecord;

private FullOperationSimulationDataImpl(@NotNull LensContext<?> lensContext) {
private FullOperationSimulationDataImpl(@NotNull LensContext<?> lensContext, @NotNull OperationResult resultToRecord) {
assert resultToRecord.isClosed();
this.lensContext = lensContext;
this.resultToRecord = resultToRecord;
}

public static FullOperationSimulationDataImpl with(@NotNull LensContext<?> lensContext) {
return new FullOperationSimulationDataImpl(lensContext);
public static FullOperationSimulationDataImpl with(
@NotNull LensContext<?> lensContext, @NotNull OperationResult resultToRecord) {
return new FullOperationSimulationDataImpl(lensContext, resultToRecord);
}

public @NotNull LensContext<?> getLensContext() {
return lensContext;
}

@NotNull OperationResult getResultToRecord() {
return resultToRecord;
}
}

0 comments on commit 3619ed4

Please sign in to comment.