Skip to content

Commit

Permalink
Consistency update: Schroedinger's Shadow (MID-3603)
Browse files Browse the repository at this point in the history
  • Loading branch information
semancik committed Aug 2, 2018
1 parent 9fb5ef1 commit 887e4f9
Show file tree
Hide file tree
Showing 13 changed files with 329 additions and 180 deletions.
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017 Evolveum
* Copyright (c) 2017-2018 Evolveum
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -35,6 +35,13 @@
public class AsynchronousOperationResult implements ShortDumpable {

private OperationResult operationResult;

/**
* Quantum operation is an operation where the results may not be immediately obvious.
* E.g. delete on a semi-manual resource. The resource object is in fact deleted, but
* it is not yet applied to the state that we see in the backing store (CSV file).
*/
private boolean quantumOperation;

public OperationResult getOperationResult() {
return operationResult;
Expand All @@ -44,6 +51,14 @@ public void setOperationResult(OperationResult operationResult) {
this.operationResult = operationResult;
}

public boolean isQuantumOperation() {
return quantumOperation;
}

public void setQuantumOperation(boolean quantumOperation) {
this.quantumOperation = quantumOperation;
}

public static AsynchronousOperationResult wrap(OperationResult result) {
AsynchronousOperationResult ret = new AsynchronousOperationResult();
ret.setOperationResult(result);
Expand All @@ -56,6 +71,9 @@ public boolean isInProgress() {

@Override
public void shortDump(StringBuilder sb) {
if (quantumOperation) {
sb.append("[QUANTUM]");
}
if (operationResult != null) {
sb.append(operationResult.getStatus());
}
Expand Down
Expand Up @@ -510,7 +510,18 @@ public static boolean isExists(ShadowType shadow) {
Boolean exists = shadow.isExists();
return exists == null || exists;
}

public static boolean isTombstone(PrismObject<ShadowType> shadow) {
return isTombstone(shadow.asObjectable());
}

public static boolean isTombstone(ShadowType shadowType) {
return isDead(shadowType) && !isExists(shadowType);
}

public static boolean isSchroedinger(ShadowType shadowType) {
return isDead(shadowType) && isExists(shadowType);
}

public static boolean matches(ShadowType shadowType, String resourceOid, ShadowKindType kind, String intent) {
if (shadowType == null) {
Expand Down Expand Up @@ -770,4 +781,5 @@ private static <T> void validateAttribute(ResourceAttribute<T> attribute,
}
}


}
Expand Up @@ -1179,6 +1179,10 @@ public void test302RecomputeWill() throws Exception {

/**
* Case is closed. The operation is complete.
* However, in the semi-manual case this gets really interesting.
* We have Schroedinger's shadow here. deleted account, ticket closed, account is deleted
* by administrator in the target system. But the account is still in the backing store (CSV)
* because scheduled export has not refreshed the file yet.
*/
@Test
public void test310CloseCaseAndRecomputeWill() throws Exception {
Expand All @@ -1204,43 +1208,45 @@ public void test310CloseCaseAndRecomputeWill() throws Exception {

accountWillCompletionTimestampEnd = clock.currentTimeXMLGregorianCalendar();

PrismObject<ShadowType> shadowRepo = repositoryService.getObject(ShadowType.class, accountWillOid, null, result);
display("Repo shadow", shadowRepo);
assertSinglePendingOperation(shadowRepo,
accountWillReqestTimestampStart, accountWillReqestTimestampEnd,
OperationResultStatusType.SUCCESS,
accountWillCompletionTimestampStart, accountWillCompletionTimestampEnd);
assertUnassignedShadow(shadowRepo, null);
ShadowAsserter<Void> shadowRepoAsserter = assertRepoShadow(accountWillOid)
.pendingOperations()
.singleOperation()
.assertRequestTimestamp(accountWillReqestTimestampStart, accountWillReqestTimestampEnd)
.assertExecutionStatus(PendingOperationExecutionStatusType.COMPLETED)
.assertResultStatus(OperationResultStatusType.SUCCESS)
.assertCompletionTimestamp(accountWillCompletionTimestampStart, accountWillCompletionTimestampEnd)
.end()
.end();
assertUnassignedShadow(shadowRepoAsserter, false, null);

PrismObject<ShadowType> shadowModel = modelService.getObject(ShadowType.class,
modelService.getObject(ShadowType.class,
accountWillOid, null, task, result);

display("Model shadow", shadowModel);
ShadowType shadowTypeProvisioning = shadowModel.asObjectable();
assertShadowName(shadowModel, USER_WILL_NAME);
assertEquals("Wrong kind (provisioning)", ShadowKindType.ACCOUNT, shadowTypeProvisioning.getKind());
assertUnassignedShadow(shadowModel, ActivationStatusType.ENABLED); // backing store not yet updated
assertShadowPassword(shadowModel);

PendingOperationType pendingOperation = assertSinglePendingOperation(shadowModel,
accountWillReqestTimestampStart, accountWillReqestTimestampEnd,
OperationResultStatusType.SUCCESS,
accountWillCompletionTimestampStart, accountWillCompletionTimestampEnd);
ShadowAsserter<Void> shadowModelAsserter = assertModelShadow(accountWillOid)
.assertName(USER_WILL_NAME)
.assertKind(ShadowKindType.ACCOUNT)
.pendingOperations()
.singleOperation()
.assertRequestTimestamp(accountWillReqestTimestampStart, accountWillReqestTimestampEnd)
.assertExecutionStatus(PendingOperationExecutionStatusType.COMPLETED)
.assertResultStatus(OperationResultStatusType.SUCCESS)
.assertCompletionTimestamp(accountWillCompletionTimestampStart, accountWillCompletionTimestampEnd)
.end()
.end();
assertUnassignedShadow(shadowModelAsserter, false, ActivationStatusType.ENABLED); // backing store not yet updated
assertShadowPassword(shadowModelAsserter);

PrismObject<ShadowType> shadowModelFuture = modelService.getObject(ShadowType.class,
accountWillOid,
SelectorOptions.createCollection(GetOperationOptions.createPointInTimeType(PointInTimeType.FUTURE)),
task, result);
display("Model shadow (future)", shadowModelFuture);
PrismObject<ShadowType> shadowModelFuture = assertModelShadowFuture(accountWillOid)
.getObject();
assertWillUnassignedFuture(shadowModelFuture, true);

assertCase(willLastCaseOid, SchemaConstants.CASE_STATE_CLOSED);

assertSteadyResources();
}

protected void assertUnassignedShadow(PrismObject<ShadowType> shadow, ActivationStatusType expectAlternativeActivationStatus) {
assertShadowDead(shadow);
protected void assertUnassignedShadow(ShadowAsserter<?> shadowModelAsserter, boolean backingStoreUpdated, ActivationStatusType expectAlternativeActivationStatus) {
shadowModelAsserter.assertTombstone();
}

/**
Expand All @@ -1258,34 +1264,36 @@ public void test320RecomputeWillAfter5min() throws Exception {

// WHEN
displayWhen(TEST_NAME);
recomputeUser(userWillOid, task, result);
reconcileUser(userWillOid, task, result);

// THEN
displayThen(TEST_NAME);
assertSuccess(result);

PrismObject<ShadowType> shadowRepo = repositoryService.getObject(ShadowType.class, accountWillOid, null, result);
display("Repo shadow", shadowRepo);
assertSinglePendingOperation(shadowRepo,
accountWillReqestTimestampStart, accountWillReqestTimestampEnd,
OperationResultStatusType.SUCCESS,
accountWillCompletionTimestampStart, accountWillCompletionTimestampEnd);
assertUnassignedShadow(shadowRepo, null);

PrismObject<ShadowType> shadowModel = modelService.getObject(ShadowType.class,
accountWillOid, null, task, result);

display("Model shadow", shadowModel);
ShadowType shadowTypeProvisioning = shadowModel.asObjectable();
assertShadowName(shadowModel, USER_WILL_NAME);
assertEquals("Wrong kind (provisioning)", ShadowKindType.ACCOUNT, shadowTypeProvisioning.getKind());
assertUnassignedShadow(shadowModel, ActivationStatusType.ENABLED); // backing store not yet updated
assertShadowPassword(shadowModel);

PendingOperationType pendingOperation = assertSinglePendingOperation(shadowModel,
accountWillReqestTimestampStart, accountWillReqestTimestampEnd,
OperationResultStatusType.SUCCESS,
accountWillCompletionTimestampStart, accountWillCompletionTimestampEnd);
ShadowAsserter<Void> shadowRepoAsserter = assertRepoShadow(accountWillOid)
.pendingOperations()
.singleOperation()
.assertRequestTimestamp(accountWillReqestTimestampStart, accountWillReqestTimestampEnd)
.assertExecutionStatus(PendingOperationExecutionStatusType.COMPLETED)
.assertResultStatus(OperationResultStatusType.SUCCESS)
.assertCompletionTimestamp(accountWillCompletionTimestampStart, accountWillCompletionTimestampEnd)
.end()
.end();
assertUnassignedShadow(shadowRepoAsserter, false, null);

ShadowAsserter<Void> shadowModelAsserter = assertModelShadow(accountWillOid)
.assertName(USER_WILL_NAME)
.assertKind(ShadowKindType.ACCOUNT)
.pendingOperations()
.singleOperation()
.assertRequestTimestamp(accountWillReqestTimestampStart, accountWillReqestTimestampEnd)
.assertExecutionStatus(PendingOperationExecutionStatusType.COMPLETED)
.assertResultStatus(OperationResultStatusType.SUCCESS)
.assertCompletionTimestamp(accountWillCompletionTimestampStart, accountWillCompletionTimestampEnd)
.end()
.end();
assertUnassignedShadow(shadowModelAsserter, false, ActivationStatusType.ENABLED); // backing store not yet updated
assertShadowPassword(shadowModelAsserter);

PrismObject<ShadowType> shadowModelFuture = modelService.getObject(ShadowType.class,
accountWillOid,
Expand All @@ -1299,6 +1307,10 @@ public void test320RecomputeWillAfter5min() throws Exception {
assertSteadyResources();
}

/**
* For semi-manual case this is the place where the quantum state of Schroedinger's
* shadow collapses. From now on we should have ordinary tombstone shadow.
*/
@Test
public void test330UpdateBackingStoreAndRecomputeWill() throws Exception {
final String TEST_NAME = "test330UpdateBackingStoreAndRecomputeWill";
Expand All @@ -1312,33 +1324,39 @@ public void test330UpdateBackingStoreAndRecomputeWill() throws Exception {

// WHEN
displayWhen(TEST_NAME);
recomputeUser(userWillOid, task, result);
// Reconcile is needed here. Recompute means noFetch which means that we won't
// discover that an account is missing from backing store which means that the
// quantum state won't collapse.
reconcileUser(userWillOid, task, result);

// THEN
displayThen(TEST_NAME);
assertSuccess(result);

PrismObject<ShadowType> shadowRepo = repositoryService.getObject(ShadowType.class, accountWillOid, null, result);
display("Repo shadow", shadowRepo);
assertSinglePendingOperation(shadowRepo,
accountWillReqestTimestampStart, accountWillReqestTimestampEnd,
OperationResultStatusType.SUCCESS,
accountWillCompletionTimestampStart, accountWillCompletionTimestampEnd);
assertUnassignedShadow(shadowRepo, null);

PrismObject<ShadowType> shadowModel = modelService.getObject(ShadowType.class,
accountWillOid, null, task, result);

display("Model shadow", shadowModel);
ShadowType shadowTypeProvisioning = shadowModel.asObjectable();
assertShadowName(shadowModel, USER_WILL_NAME);
assertEquals("Wrong kind (provisioning)", ShadowKindType.ACCOUNT, shadowTypeProvisioning.getKind());
assertUnassignedShadow(shadowModel, ActivationStatusType.DISABLED);

PendingOperationType pendingOperation = assertSinglePendingOperation(shadowModel,
accountWillReqestTimestampStart, accountWillReqestTimestampEnd,
OperationResultStatusType.SUCCESS,
accountWillCompletionTimestampStart, accountWillCompletionTimestampEnd);

ShadowAsserter<Void> shadowRepoAsserter = assertRepoShadow(accountWillOid)
.pendingOperations()
.singleOperation()
.assertRequestTimestamp(accountWillReqestTimestampStart, accountWillReqestTimestampEnd)
.assertExecutionStatus(PendingOperationExecutionStatusType.COMPLETED)
.assertResultStatus(OperationResultStatusType.SUCCESS)
.assertCompletionTimestamp(accountWillCompletionTimestampStart, accountWillCompletionTimestampEnd)
.end()
.end();
assertUnassignedShadow(shadowRepoAsserter, true, null);

ShadowAsserter<Void> shadowModelAsserter = assertModelShadow(accountWillOid)
.assertName(USER_WILL_NAME)
.assertKind(ShadowKindType.ACCOUNT)
.pendingOperations()
.singleOperation()
.assertRequestTimestamp(accountWillReqestTimestampStart, accountWillReqestTimestampEnd)
.assertExecutionStatus(PendingOperationExecutionStatusType.COMPLETED)
.assertResultStatus(OperationResultStatusType.SUCCESS)
.assertCompletionTimestamp(accountWillCompletionTimestampStart, accountWillCompletionTimestampEnd)
.end()
.end();
assertUnassignedShadow(shadowModelAsserter, true, ActivationStatusType.DISABLED);
// Do NOT assert password here. There is no password even for semi-manual case as the shadow is dead and account gone.

PrismObject<ShadowType> shadowModelFuture = modelService.getObject(ShadowType.class,
accountWillOid,
Expand All @@ -1353,6 +1371,8 @@ public void test330UpdateBackingStoreAndRecomputeWill() throws Exception {
}

// TODO: nofetch, nofetch+future

// TODO: Scheroedinger shadow, let the operation go over grace period.

/**
* ff 20min, grace period expired, but retention period not expired yet.
Expand Down Expand Up @@ -1666,8 +1686,8 @@ protected void assertWillUnassignPendingOperation(PrismObject<ShadowType> shadow
* MID-4037
*/
@Test
public void test515CloseCasesAndRecomputeWill() throws Exception {
final String TEST_NAME = "test515CloseCasesAndRecomputeWill";
public void test515CloseCasesAndReconcileWill() throws Exception {
final String TEST_NAME = "test515CloseCasesAndReconcileWill";
displayTestTitle(TEST_NAME);
// GIVEN
Task task = createTask(TEST_NAME);
Expand All @@ -1690,31 +1710,26 @@ public void test515CloseCasesAndRecomputeWill() throws Exception {

accountWillCompletionTimestampEnd = clock.currentTimeXMLGregorianCalendar();

PrismObject<ShadowType> shadowRepo = repositoryService.getObject(ShadowType.class, accountWillOid, null, result);
display("Repo shadow", shadowRepo);

assertPendingOperationDeltas(shadowRepo, 2);

PendingOperationType pendingOperation = findPendingOperation(shadowRepo,
OperationResultStatusType.SUCCESS, ChangeTypeType.ADD);
assertPendingOperation(shadowRepo, pendingOperation,
accountWillReqestTimestampStart, accountWillReqestTimestampEnd,
OperationResultStatusType.SUCCESS,
accountWillCompletionTimestampStart, accountWillCompletionTimestampEnd);
assertNotNull("No ID in pending operation", pendingOperation.getId());

assertWillUnassignPendingOperation(shadowRepo, OperationResultStatusType.SUCCESS);

assertUnassignedShadow(shadowRepo, null);

PrismObject<ShadowType> shadowModel = modelService.getObject(ShadowType.class,
accountWillOid, null, task, result);
ShadowAsserter<Void> shadowRepoAsserter = assertRepoShadow(accountWillOid)
.pendingOperations()
.assertOperations(2)
.by()
.executionStatus(PendingOperationExecutionStatusType.COMPLETED)
.changeType(ChangeTypeType.ADD)
.find()
.assertRequestTimestamp(accountWillReqestTimestampStart, accountWillReqestTimestampEnd)
.assertResultStatus(OperationResultStatusType.SUCCESS)
.assertCompletionTimestamp(accountWillCompletionTimestampStart, accountWillCompletionTimestampEnd)
.assertId()
.end()
.end();
assertWillUnassignPendingOperation(shadowRepoAsserter.getObject(), OperationResultStatusType.SUCCESS);
assertUnassignedShadow(shadowRepoAsserter, true, null);

display("Model shadow", shadowModel);
ShadowType shadowTypeProvisioning = shadowModel.asObjectable();
assertShadowName(shadowModel, USER_WILL_NAME);
assertEquals("Wrong kind (provisioning)", ShadowKindType.ACCOUNT, shadowTypeProvisioning.getKind());
assertUnassignedShadow(shadowModel, null); // Shadow in not in the backing store
ShadowAsserter<Void> shadowModelAsserter = assertModelShadow(accountWillOid)
.assertName(USER_WILL_NAME)
.assertKind(ShadowKindType.ACCOUNT);
assertUnassignedShadow(shadowModelAsserter, true, null); // Shadow in not in the backing store

PrismObject<ShadowType> shadowModelFuture = modelService.getObject(ShadowType.class,
accountWillOid,
Expand Down
Expand Up @@ -78,6 +78,7 @@
import com.evolveum.midpoint.schema.util.ResourceTypeUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.test.IntegrationTestTools;
import com.evolveum.midpoint.test.asserter.ShadowAsserter;
import com.evolveum.midpoint.test.util.TestUtil;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
Expand Down Expand Up @@ -2318,6 +2319,10 @@ protected void assertShadowActivationAdministrativeStatus(PrismObject<ShadowType
assertActivationAdministrativeStatus(shadow, expectedStatus);
}

protected void assertShadowPassword(ShadowAsserter<?> shadowAsserter) {
assertShadowPassword(shadowAsserter.getObject());
}

protected void assertShadowPassword(PrismObject<ShadowType> shadow) {
// pure manual resource should never "read" password
assertNoShadowPassword(shadow);
Expand Down

0 comments on commit 887e4f9

Please sign in to comment.