Skip to content

Commit

Permalink
Improve error handling in async. provisioning
Browse files Browse the repository at this point in the history
Instead of reporting everything as SystemException we now try
to distinguish between specific kinds of errors. This is done
for JMS targets for now.

Should resolve MID-7069.
  • Loading branch information
mederly committed May 25, 2021
1 parent 2a46d0c commit a8b5899
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 124 deletions.
Expand Up @@ -7,8 +7,11 @@

package com.evolveum.midpoint.provisioning.ucf.api.async;

import com.evolveum.midpoint.provisioning.ucf.api.GenericFrameworkException;
import com.evolveum.midpoint.schema.result.OperationResult;

import com.evolveum.midpoint.util.exception.*;

import com.google.common.annotations.VisibleForTesting;
import org.jetbrains.annotations.NotNull;

Expand All @@ -34,17 +37,21 @@ public interface AsyncProvisioningTarget {
/**
* Creates a copy of the target - in the initial (unconnected) state.
*/
@NotNull AsyncProvisioningTarget copy();
@NotNull AsyncProvisioningTarget copy() throws ConfigurationException;

/**
* Tests this target for reachability.
*/
void test(OperationResult result);
void test(OperationResult result)
throws CommunicationException, GenericFrameworkException, SchemaException, ObjectAlreadyExistsException,
ConfigurationException, SecurityViolationException, PolicyViolationException;

/**
* Sends out a request to this target.
* Throws an exception if the operation is not successful.
* @return Asynchronous operation reference
*/
String send(AsyncProvisioningRequest request, OperationResult result);
String send(AsyncProvisioningRequest request, OperationResult result)
throws CommunicationException, GenericFrameworkException, SchemaException, ObjectAlreadyExistsException,
ConfigurationException, SecurityViolationException, PolicyViolationException;
}
Expand Up @@ -128,32 +128,37 @@ public void configure(@NotNull PrismContainerValue<?> configuration, List<QName>
throws SchemaException, ConfigurationException {

OperationResult result = parentResult.createSubresult(ConnectorInstance.OPERATION_CONFIGURE);
try {

PrismContainerValue<?> mutableConfiguration;
if (configuration.isImmutable()) {
mutableConfiguration = configuration.clone();
} else {
mutableConfiguration = configuration;
}
PrismContainerValue<?> mutableConfiguration;
if (configuration.isImmutable()) {
mutableConfiguration = configuration.clone();
} else {
mutableConfiguration = configuration;
}

mutableConfiguration.applyDefinition(getConfigurationContainerDefinition());
setConnectorConfiguration(mutableConfiguration);
applyConfigurationToConfigurationClass(mutableConfiguration);
mutableConfiguration.applyDefinition(getConfigurationContainerDefinition());
setConnectorConfiguration(mutableConfiguration);
applyConfigurationToConfigurationClass(mutableConfiguration);

// TODO: transform configuration in a subclass
// TODO: transform configuration in a subclass

if (configured) {
disconnect(result);
}
if (configured) {
disconnect(result);
}

connect(result);
connect(result);

configured = true;
configured = true;

result.recordSuccessIfUnknown();
result.recordSuccessIfUnknown();
} catch (Throwable t) {
result.recordFatalError(t);
throw t;
}
}

protected abstract void connect(OperationResult result);
protected abstract void connect(OperationResult result) throws ConfigurationException;

protected abstract void disconnect(OperationResult result);

Expand Down
Expand Up @@ -6,6 +6,7 @@
*/
package com.evolveum.midpoint.provisioning.ucf.impl.builtin.async.provisioning;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
Expand All @@ -14,8 +15,8 @@
import com.evolveum.midpoint.provisioning.ucf.api.async.AsyncProvisioningRequest;

import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.*;

Expand Down Expand Up @@ -99,14 +100,14 @@ public void setConfiguration(ConnectorConfiguration configuration) {
}

@NotNull
private List<AsyncProvisioningTarget> createNewTargets() {
private List<AsyncProvisioningTarget> createNewTargets() throws ConfigurationException {
return unmodifiableList(targetManager.createTargets(configuration.getAllTargets()));
}

/**
* Gracefully shuts down existing target and then tries to replace it with its new instantiation.
*/
private AsyncProvisioningTarget restartTarget(AsyncProvisioningTarget target) {
private AsyncProvisioningTarget restartTarget(AsyncProvisioningTarget target) throws ConfigurationException {
target.disconnect();

List<AsyncProvisioningTarget> existingTargets = targetsReference.get();
Expand Down Expand Up @@ -139,7 +140,7 @@ private List<AsyncProvisioningTarget> updateTargetsList(List<AsyncProvisioningTa
}

@Override
protected void connect(OperationResult result) {
protected void connect(OperationResult result) throws ConfigurationException {
List<AsyncProvisioningTarget> newTargets = createNewTargets();
newTargets.forEach(AsyncProvisioningTarget::connect);
targetsReference.set(newTargets);
Expand All @@ -156,7 +157,14 @@ public void test(OperationResult parentResult) {
result.addContext(OperationResult.CONTEXT_IMPLEMENTATION_CLASS, AsyncProvisioningConnectorInstance.class);
result.addContext("connector", getConnectorObject().toString());
try {
targetsReference.get().forEach(t -> t.test(result));
targetsReference.get().forEach(t -> {
try {
t.test(result);
} catch (Exception e) {
// Assuming the exception is recorded in the result.
LoggingUtils.logException(LOGGER, "Exception encountered while testing connection", e);
}
});
result.computeStatus();
} catch (RuntimeException e) {
result.recordFatalError("Couldn't test async provisioning targets: " + e.getMessage(), e);
Expand All @@ -165,7 +173,9 @@ public void test(OperationResult parentResult) {

@Override
public AsynchronousOperationReturnValue<Collection<ResourceAttribute<?>>> addObject(PrismObject<? extends ShadowType> object,
StateReporter reporter, OperationResult parentResult) {
StateReporter reporter, OperationResult parentResult) throws CommunicationException, GenericFrameworkException,
SchemaException, ObjectAlreadyExistsException, ConfigurationException, SecurityViolationException,
PolicyViolationException {
InternalMonitor.recordConnectorOperation("addObject");
OperationResult result = parentResult.createSubresult(OP_ADD_OBJECT);
try {
Expand All @@ -182,7 +192,9 @@ public AsynchronousOperationReturnValue<Collection<ResourceAttribute<?>>> addObj
@Override
public AsynchronousOperationReturnValue<Collection<PropertyModificationOperation>> modifyObject(
ResourceObjectIdentification identification, PrismObject<ShadowType> shadow, @NotNull Collection<Operation> changes,
ConnectorOperationOptions options, StateReporter reporter, OperationResult parentResult) {
ConnectorOperationOptions options, StateReporter reporter, OperationResult parentResult)
throws CommunicationException, GenericFrameworkException, SchemaException, ObjectAlreadyExistsException,
ConfigurationException, SecurityViolationException, PolicyViolationException {
InternalMonitor.recordConnectorOperation("modifyObject");
OperationResult result = parentResult.createSubresult(OP_MODIFY_OBJECT);
try {
Expand All @@ -200,7 +212,9 @@ public AsynchronousOperationReturnValue<Collection<PropertyModificationOperation
@Override
public AsynchronousOperationResult deleteObject(ObjectClassComplexTypeDefinition objectClass,
PrismObject<ShadowType> shadow, Collection<? extends ResourceAttribute<?>> identifiers, StateReporter reporter,
OperationResult parentResult) throws SchemaException {
OperationResult parentResult)
throws CommunicationException, GenericFrameworkException, SchemaException, ConfigurationException,
SecurityViolationException, PolicyViolationException {
InternalMonitor.recordConnectorOperation("deleteObject");
OperationResult result = parentResult.createSubresult(OP_DELETE_OBJECT);
try {
Expand All @@ -216,8 +230,17 @@ public AsynchronousOperationResult deleteObject(ObjectClassComplexTypeDefinition
}

private <X> AsynchronousOperationReturnValue<X> createAndSendRequest(OperationRequested operation, Task task,
OperationResult result) {
AsyncProvisioningRequest request = transformer.transformOperationRequested(operation, task, result);
OperationResult result)
throws CommunicationException, GenericFrameworkException, SchemaException, ConfigurationException,
SecurityViolationException, PolicyViolationException {
AsyncProvisioningRequest request;
try {
request = transformer.transformOperationRequested(operation, task, result);
} catch (ExpressionEvaluationException | IOException | ObjectNotFoundException e) {
// We roughly assume that expression evaluation exception means that there is a problem with the expression itself.
// For IOException and ObjectNotFoundException it is unclear. But we cannot do much more here, anyway.
throw new ConfigurationException("Couldn't evaluate transformation expression: " + e.getMessage(), e);
}
String asyncOperationReference = sendRequest(request, result);

AsynchronousOperationReturnValue<X> ret = new AsynchronousOperationReturnValue<>();
Expand All @@ -232,7 +255,9 @@ private <X> AsynchronousOperationReturnValue<X> createAndSendRequest(OperationRe
return ret;
}

private String sendRequest(AsyncProvisioningRequest request, OperationResult result) {
private String sendRequest(AsyncProvisioningRequest request, OperationResult result)
throws CommunicationException, GenericFrameworkException, SchemaException, ConfigurationException,
SecurityViolationException, PolicyViolationException {
List<AsyncProvisioningTarget> targets = targetsReference.get();
if (targets.isEmpty()) {
throw new IllegalStateException("No targets available");
Expand All @@ -244,7 +269,15 @@ private String sendRequest(AsyncProvisioningRequest request, OperationResult res
return target.send(request, result);
} catch (Throwable t) {
LOGGER.warn("Couldn't send a request to target {}, restarting the target and trying again", target, t);
AsyncProvisioningTarget restartedTarget = restartTarget(target);
AsyncProvisioningTarget restartedTarget;
try {
restartedTarget = restartTarget(target);
} catch (Throwable t3) {
LOGGER.warn("Target {} couldn't be restarted, trying the next one", target, t3);
lastException = t3;
continue;
}

if (restartedTarget != null) {
try {
result.muteLastSubresultError();
Expand All @@ -259,9 +292,25 @@ private String sendRequest(AsyncProvisioningRequest request, OperationResult res
}
}
}
assert lastException != null;
throw new SystemException("Couldn't send request to any of the targets available. Last exception: " +
lastException.getMessage(), lastException);
if (lastException instanceof CommunicationException) {
throw (CommunicationException) lastException;
} else if (lastException instanceof GenericFrameworkException) {
throw (GenericFrameworkException) lastException;
} else if (lastException instanceof SchemaException) {
throw (SchemaException) lastException;
} else if (lastException instanceof ConfigurationException) {
throw (ConfigurationException) lastException;
} else if (lastException instanceof SecurityViolationException) {
throw (SecurityViolationException) lastException;
} else if (lastException instanceof PolicyViolationException) {
throw (PolicyViolationException) lastException;
} else if (lastException instanceof RuntimeException) {
throw (RuntimeException) lastException;
} else if (lastException instanceof Error) {
throw (Error) lastException;
} else {
throw new IllegalStateException("Unknown exception while sending request", lastException);
}
}

//region Trivia
Expand Down
Expand Up @@ -47,48 +47,44 @@ public OperationRequestTransformer(@NotNull AsyncProvisioningConnectorInstance c

@NotNull
public AsyncProvisioningRequest transformOperationRequested(@NotNull OperationRequested operationRequested,
Task task, OperationResult result) {
Task task, OperationResult result) throws SchemaException, IOException, ExpressionEvaluationException,
SecurityViolationException, CommunicationException, ConfigurationException, ObjectNotFoundException {

try {
PredefinedOperationRequestTransformationType predefinedTransformation = connectorInstance.getPredefinedTransformation();
if (predefinedTransformation != null) {
return transformerHelper.applyPredefinedTransformation(operationRequested, predefinedTransformation);
}
PredefinedOperationRequestTransformationType predefinedTransformation = connectorInstance.getPredefinedTransformation();
if (predefinedTransformation != null) {
return transformerHelper.applyPredefinedTransformation(operationRequested, predefinedTransformation);
}

ExpressionType transformExpression = connectorInstance.getTransformExpression();
if (transformExpression != null) {

ExpressionType transformExpression = connectorInstance.getTransformExpression();
if (transformExpression != null) {

VariablesMap variables = new VariablesMap();
variables.put(VAR_OPERATION_REQUESTED, operationRequested, operationRequested.getClass());
variables.put(VAR_TRANSFORMER_HELPER, transformerHelper, TransformerHelper.class);
variables.put(VAR_REQUEST_FORMATTER, transformerHelper.jsonRequestFormatter(operationRequested), JsonRequestFormatter.class);

List<?> list = connectorInstance.getUcfExpressionEvaluator().evaluate(transformExpression, variables,
SchemaConstantsGenerated.C_ASYNC_PROVISIONING_REQUEST, "creating asynchronous provisioning request",
task, result);
if (list.isEmpty()) {
throw new IllegalStateException("Transformational script returned no value");
}
if (list.size() > 1) {
throw new IllegalStateException("Transformational script returned more than single value: " + list);
}
Object o = list.get(0);
if (o == null) {
// In the future we can call e.g. default request creator here
throw new IllegalStateException("Transformational script returned no value");
} else if (o instanceof AsyncProvisioningRequest) {
return (AsyncProvisioningRequest) o;
} else if (o instanceof String) {
return StringAsyncProvisioningRequest.of((String) o);
} else {
throw new IllegalStateException("Transformational script should provide an AsyncProvisioningRequest but created " + MiscUtil.getClass(o) + " instead");
}
VariablesMap variables = new VariablesMap();
variables.put(VAR_OPERATION_REQUESTED, operationRequested, operationRequested.getClass());
variables.put(VAR_TRANSFORMER_HELPER, transformerHelper, TransformerHelper.class);
variables.put(VAR_REQUEST_FORMATTER, transformerHelper.jsonRequestFormatter(operationRequested), JsonRequestFormatter.class);

List<?> list = connectorInstance.getUcfExpressionEvaluator().evaluate(transformExpression, variables,
SchemaConstantsGenerated.C_ASYNC_PROVISIONING_REQUEST, "creating asynchronous provisioning request",
task, result);
if (list.isEmpty()) {
throw new IllegalStateException("Transformational script returned no value");
}
if (list.size() > 1) {
throw new IllegalStateException("Transformational script returned more than single value: " + list);
}
Object o = list.get(0);
if (o == null) {
// In the future we can call e.g. default request creator here
throw new IllegalStateException("Transformational script returned no value");
} else if (o instanceof AsyncProvisioningRequest) {
return (AsyncProvisioningRequest) o;
} else if (o instanceof String) {
return StringAsyncProvisioningRequest.of((String) o);
} else {
return transformerHelper.applyPredefinedTransformation(operationRequested, SIMPLIFIED_JSON);
throw new IllegalStateException("Transformational script should provide an AsyncProvisioningRequest but created " + MiscUtil.getClass(o) + " instead");
}
} catch (RuntimeException | SchemaException | ObjectNotFoundException | SecurityViolationException |
CommunicationException | ConfigurationException | ExpressionEvaluationException | IOException e) {
throw new SystemException("Couldn't evaluate message transformation expression: " + e.getMessage(), e);
} else {
return transformerHelper.applyPredefinedTransformation(operationRequested, SIMPLIFIED_JSON);
}
}
}

0 comments on commit a8b5899

Please sign in to comment.