From 2bbe6e39aee82a729a7998875f384212e0289e41 Mon Sep 17 00:00:00 2001 From: Radovan Semancik Date: Wed, 8 Jun 2016 20:39:46 +0200 Subject: [PATCH 1/2] Fixing many many consistency issues. Some tests still failing. (MID-3093, MID-2134) --- .../midpoint/schema/GetOperationOptions.java | 42 ++- .../model/impl/lens/LensElementContext.java | 7 + .../impl/lens/LensProjectionContext.java | 25 ++ .../impl/lens/projector/ContextLoader.java | 135 ++++++---- .../impl/lens/projector/InboundProcessor.java | 23 +- .../projector/ReconciliationProcessor.java | 6 +- .../model/intest/TestMultiResource.java | 248 +++++++++++++++++- .../src/test/resources/logback-test.xml | 2 +- .../test/AbstractModelIntegrationTest.java | 6 + .../impl/ObjectNotFoundHandler.java | 36 ++- .../ucf/impl/ConnectorInstanceIcfImpl.java | 18 +- 11 files changed, 463 insertions(+), 85 deletions(-) diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/GetOperationOptions.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/GetOperationOptions.java index e76c94ca24a..0b2ed4fd605 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/GetOperationOptions.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/GetOperationOptions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2013 Evolveum + * Copyright (c) 2010-2016 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -415,9 +415,43 @@ public GetOperationOptions clone() { @Override public String toString() { - return "GetOperationOptions(resolve=" + resolve + ", resolveNames=" + resolveNames + ",noFetch=" + noFetch - + ", raw=" + raw + ", doNotDiscovery="+doNotDiscovery+", retrieve="+retrieve+", allowNotFound="+ allowNotFound - +", relationalValueSearchQuery="+relationalValueSearchQuery+")"; + StringBuilder sb = new StringBuilder("GetOperationOptions("); + appendFlag(sb, "resolve", resolve); + appendFlag(sb, "resolveNames", resolveNames); + appendFlag(sb, "noFetch", noFetch); + appendFlag(sb, "raw", raw); + appendFlag(sb, "doNotDiscovery", doNotDiscovery); + appendVal(sb, "retrieve", retrieve); + appendFlag(sb, "allowNotFound", allowNotFound); + appendVal(sb, "relationalValueSearchQuery", relationalValueSearchQuery); + if (sb.charAt(sb.length() - 1) == ',') { + sb.deleteCharAt(sb.length() - 1); + } + sb.append(")"); + return sb.toString(); + } + + private void appendFlag(StringBuilder sb, String name, Boolean val) { + if (val == null) { + return; + } else if (val) { + sb.append(name); + sb.append(","); + } else { + sb.append(name); + sb.append("=false,"); + } + } + + private void appendVal(StringBuilder sb, String name, Object val) { + if (val == null) { + return; + } else { + sb.append(name); + sb.append("="); + sb.append(val); + sb.append(","); + } } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensElementContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensElementContext.java index 976da28a385..3f3b688bfe8 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensElementContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensElementContext.java @@ -16,11 +16,18 @@ package com.evolveum.midpoint.model.impl.lens; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import javax.xml.namespace.QName; + import com.evolveum.midpoint.prism.ConsistencyCheckScope; import com.evolveum.midpoint.prism.Objectable; import com.evolveum.midpoint.schema.DeltaConvertor; +import com.evolveum.midpoint.schema.constants.MidPointConstants; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.processor.ResourceAttribute; +import com.evolveum.midpoint.schema.processor.ResourceAttributeContainer; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java index f9a8fac1b16..da17f2d8427 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java @@ -30,6 +30,7 @@ import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.schema.DeltaConvertor; import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; +import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; @@ -52,6 +53,7 @@ import com.evolveum.midpoint.prism.delta.ReferenceDelta; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.schema.processor.ResourceAttribute; +import com.evolveum.midpoint.schema.processor.ResourceAttributeContainer; import com.evolveum.midpoint.schema.processor.ResourceSchema; import com.evolveum.midpoint.schema.util.MiscSchemaUtil; import com.evolveum.midpoint.schema.util.ShadowUtil; @@ -906,6 +908,29 @@ public void checkConsistence(String contextDesc, boolean fresh, boolean force) { } } + @Override + protected void checkConsistence(PrismObject object, String elementDesc, String contextDesc) { + super.checkConsistence(object, elementDesc, contextDesc); + ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(object); + if (attributesContainer != null) { + ResourceType resource = getResource(); + if (resource != null) { + String resourceNamespace = ResourceTypeUtil.getResourceNamespace(resource); + for(ResourceAttribute attribute: attributesContainer.getAttributes()) { + QName attrName = attribute.getElementName(); + if (SchemaConstants.NS_ICF_SCHEMA.equals(attrName.getNamespaceURI())) { + continue; + } + if (resourceNamespace.equals(attrName.getNamespaceURI())) { + continue; + } + String desc = elementDesc+" in "+this + (contextDesc == null ? "" : " in " +contextDesc); + throw new IllegalStateException("Invalid namespace for attribute "+attrName+" in "+desc); + } + } + } + } + protected boolean isRequireSecondardyDeltaOid() { if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD || synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN || diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ContextLoader.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ContextLoader.java index b5a2e762336..4fb2cb54bc0 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ContextLoader.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ContextLoader.java @@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; +import com.evolveum.midpoint.common.InternalsConfig; import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.model.api.ModelExecuteOptions; import com.evolveum.midpoint.model.api.PolicyViolationException; @@ -517,7 +518,7 @@ private void loadLinkRefsFromFocus(LensContext context, } LensProjectionContext accountContext = getOrCreateAccountContext(context, shadow, task, result); accountContext.setFresh(true); - accountContext.setExists(true); + accountContext.setExists(shadow != null); if (context.isDoReconciliationForAllProjections()) { accountContext.setDoReconciliation(true); } @@ -720,57 +721,74 @@ private void loadLinkRefsFromDelta(LensContext context, private void loadProjectionContextsSync(LensContext context, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - for (LensProjectionContext accountCtx : context.getProjectionContexts()) { - if (accountCtx.isFresh() && accountCtx.getObjectCurrent() != null) { + for (LensProjectionContext projCtx : context.getProjectionContexts()) { + if (projCtx.isFresh() && projCtx.getObjectCurrent() != null) { // already loaded continue; } - ObjectDelta syncDelta = accountCtx.getSyncDelta(); + ObjectDelta syncDelta = projCtx.getSyncDelta(); if (syncDelta != null) { - if (accountCtx.isDoReconciliation()) { + if (projCtx.isDoReconciliation()) { // Do not load old account now. It will get loaded later in the // reconciliation step. Just mark it as fresh. - accountCtx.setFresh(true); + projCtx.setFresh(true); continue; } String oid = syncDelta.getOid(); - PrismObject account = null; + PrismObject shadow = null; + if (syncDelta.getChangeType() == ChangeType.ADD) { - account = syncDelta.getObjectToAdd().clone(); - accountCtx.setLoadedObject(account); - accountCtx.setExists(true); + shadow = syncDelta.getObjectToAdd().clone(); + projCtx.setLoadedObject(shadow); + projCtx.setExists(true); + } else { + if (oid == null) { - throw new IllegalArgumentException("No OID in sync delta in " + accountCtx); + throw new IllegalArgumentException("No OID in sync delta in " + projCtx); } // Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here. // We need to fetch from provisioning and not repository so the correct definition will be set. - Collection> options = SelectorOptions.createCollection(GetOperationOptions.createNoFetch()); - account = provisioningService.getObject(ShadowType.class, oid, options, task, result); + GetOperationOptions option = GetOperationOptions.createNoFetch(); + option.setDoNotDiscovery(true); + Collection> options = SelectorOptions.createCollection(option); + + try { + + shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); + + } catch (ObjectNotFoundException e) { + LOGGER.trace("Loading shadow {} from sync delta failed: not found", oid); + projCtx.setExists(false); + projCtx.setObjectCurrent(null); + } + // We will not set old account if the delta is delete. The // account does not really exists now. // (but the OID and resource will be set from the repo // shadow) if (syncDelta.getChangeType() == ChangeType.DELETE) { - accountCtx.setExists(false); - } else { - syncDelta.applyTo(account); - accountCtx.setLoadedObject(account); - accountCtx.setExists(true); + projCtx.setExists(false); + projCtx.getResourceShadowDiscriminator().setThombstone(true); + } else if (shadow != null) { + syncDelta.applyTo(shadow); + projCtx.setLoadedObject(shadow); + projCtx.setExists(true); } } + // Make sure OID is set correctly - accountCtx.setOid(oid); + projCtx.setOid(oid); // Make sure that resource is also resolved - if (accountCtx.getResource() == null) { - String resourceOid = ShadowUtil.getResourceOid(account.asObjectable()); + if (projCtx.getResource() == null && shadow != null) { + String resourceOid = ShadowUtil.getResourceOid(shadow.asObjectable()); if (resourceOid == null) { - throw new IllegalArgumentException("No resource OID in " + account); + throw new IllegalArgumentException("No resource OID in " + shadow); } ResourceType resourceType = LensUtil.getResource(context, resourceOid, provisioningService, task, result); - accountCtx.setResource(resourceType); + projCtx.setResource(resourceType); } - accountCtx.setFresh(true); + projCtx.setFresh(true); } } } @@ -924,6 +942,8 @@ private void finishLoadOfProjectionContext(LensContext LensProjectionContext projContext, Task task, OperationResult result) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException { + + String projectionHumanReadableName = projContext.getHumanReadableName(); if (projContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { return; @@ -944,6 +964,7 @@ private void finishLoadOfProjectionContext(LensContext } // Load current object + boolean thombstone = false; PrismObject projectionObject = projContext.getObjectCurrent(); if (projContext.getObjectCurrent() == null || needToReload(context, projContext)) { if (projContext.isAdd()) { @@ -956,7 +977,7 @@ private void finishLoadOfProjectionContext(LensContext projContext.setExists(false); if (projContext.getResourceShadowDiscriminator() == null || projContext.getResourceShadowDiscriminator().getResourceOid() == null) { throw new SystemException( - "Projection with null OID, no representation and no resource OID in account sync context "+projContext); + "Projection "+projectionHumanReadableName+" with null OID, no representation and no resource OID in account sync context "+projContext); } } else { projContext.setExists(true); @@ -972,14 +993,25 @@ private void finishLoadOfProjectionContext(LensContext rootOptions.setAllowNotFound(true); Collection> options = SelectorOptions.createCollection(rootOptions); if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Loading projection shadow {}, options={}", projectionObjectOid, options); + LOGGER.trace("Loading shadow {} for projection {}, options={}", projectionObjectOid, projectionHumanReadableName, options); } - try{ + + try { PrismObject objectOld = provisioningService.getObject( projContext.getObjectTypeClass(), projectionObjectOid, options, task, result); if (LOGGER.isTraceEnabled()) { if (!GetOperationOptions.isNoFetch(rootOptions) && !GetOperationOptions.isRaw(rootOptions)) { - LOGGER.trace("Full shadow loaded: {}", objectOld); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Full shadow loaded for {}:\n{}", projectionHumanReadableName, objectOld.debugDump()); + } + } + } + if (InternalsConfig.consistencyChecks) { + String resourceOid = projContext.getResourceOid(); + if (resourceOid != null && !resourceOid.equals(objectOld.asObjectable().getResourceRef().getOid())) { + throw new IllegalStateException("Loaded shadow with wrong resourceRef. Loading shadow "+projectionObjectOid+", got "+ + objectOld.getOid()+", expected resourceRef "+resourceOid+", but was "+objectOld.asObjectable().getResourceRef().getOid()+ + " for context "+projectionHumanReadableName); } } projContext.setLoadedObject(objectOld); @@ -990,15 +1022,20 @@ private void finishLoadOfProjectionContext(LensContext projContext.setFullShadow(false); } projectionObject = objectOld; - } catch (ObjectNotFoundException ex){ - projContext.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.BROKEN); - LOGGER.warn("Could not find object with oid " + projectionObjectOid + ". Context for these object is marked as broken"); - return; - } catch (SchemaException ex){ + + } catch (ObjectNotFoundException ex) { + // This does not mean BROKEN. The projection was there, but it gone now. What we really want here + // is a thombstone projection. + thombstone = true; + projContext.setFullShadow(false); + LOGGER.warn("Could not find object with oid {}. The projection context {} is marked as thombstone.", projectionObjectOid, projectionHumanReadableName); + + } catch (SchemaException ex) { projContext.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.BROKEN); - LOGGER.warn("Schema problem while getting object with oid " + projectionObjectOid + ". Context for these object is marked as broken"); + LOGGER.warn("Schema problem while getting object with oid {}. Projection context {} is marked as broken", projectionObjectOid, projectionHumanReadableName); return; } + } projContext.setFresh(true); } @@ -1020,7 +1057,7 @@ private void finishLoadOfProjectionContext(LensContext } else if (projContext.getResourceShadowDiscriminator() != null) { resourceOid = projContext.getResourceShadowDiscriminator().getResourceOid(); } else { - throw new IllegalStateException("No shadow and no resource intent means no resource OID in "+projContext); + throw new IllegalStateException("No shadow and no resource intent means no resource OID in "+projectionHumanReadableName); } } else { resourceOid = resourceType.getOid(); @@ -1032,8 +1069,13 @@ private void finishLoadOfProjectionContext(LensContext ShadowType accountShadowType = projectionObject.asObjectable(); String intent = ShadowUtil.getIntent(accountShadowType); ShadowKindType kind = ShadowUtil.getKind(accountShadowType); - discr = new ResourceShadowDiscriminator(resourceOid, kind, intent); + discr = new ResourceShadowDiscriminator(resourceOid, kind, intent, thombstone); projContext.setResourceShadowDiscriminator(discr); + } else { + if (thombstone) { + // We do not want to reset thombstone flag if it was set before + discr.setThombstone(thombstone); + } } // Load resource @@ -1043,9 +1085,9 @@ private void finishLoadOfProjectionContext(LensContext } //Determine refined schema and password policies for account type - RefinedObjectClassDefinition rad = projContext.getStructuralObjectClassDefinition(); - if (rad != null) { - ObjectReferenceType passwordPolicyRef = rad.getPasswordPolicy(); + RefinedObjectClassDefinition structuralObjectClassDef = projContext.getStructuralObjectClassDefinition(); + if (structuralObjectClassDef != null) { + ObjectReferenceType passwordPolicyRef = structuralObjectClassDef.getPasswordPolicy(); if (passwordPolicyRef != null && passwordPolicyRef.getOid() != null) { PrismObject passwordPolicy = cacheRepositoryService.getObject( ValuePolicyType.class, passwordPolicyRef.getOid(), null, result); @@ -1065,6 +1107,8 @@ private void finishLoadOfProjectionContext(LensContext } setPrimaryDeltaOldValue(projContext); + + LOGGER.trace("FINISHED load of projection context {}\n{}", projectionHumanReadableName, projContext.debugDump()); } private boolean needToReload(LensContext context, @@ -1197,14 +1241,11 @@ public void loadFullShadow(LensContext context, LensPr } - if (!compensated) { - projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.BROKEN); - if (GetOperationOptions.isDoNotDiscovery(getOptions)) { - LOGGER.error("Load of full resource object {} resulted in ObjectNotFoundException (discovery disabled to avoid loops)", projCtx, getOptions); - throw ex; - } else { - // Setting the context to broken should be enough here. - } + if (!compensated) { + LOGGER.trace("ObjectNotFound error is not compensated, setting context to thombstone"); + projCtx.getResourceShadowDiscriminator().setThombstone(true); + projCtx.setExists(false); + projCtx.setFullShadow(false); } } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/InboundProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/InboundProcessor.java index 7664137090f..76f78c8a0e4 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/InboundProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/InboundProcessor.java @@ -150,9 +150,17 @@ void processInboundFocal(LensContext context, Task task try { for (LensProjectionContext projectionContext : context.getProjectionContexts()) { + if (projectionContext.isThombstone()) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Skipping processing of inbound expressions for projection {} because is is thombstone", projectionContext.getHumanReadableName()); + } + continue; + } if (!projectionContext.isCanProject()){ - LOGGER.trace("Skipping processing of inbound expressions for projection {}: there is a limit to propagate changes only from resource {}", - projectionContext.getResourceShadowDiscriminator(), context.getTriggeredResourceOid()); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Skipping processing of inbound expressions for projection {}: there is a limit to propagate changes only from resource {}", + projectionContext.getHumanReadableName(), context.getTriggeredResourceOid()); + } continue; } ResourceShadowDiscriminator rat = projectionContext.getResourceShadowDiscriminator(); @@ -160,15 +168,18 @@ void processInboundFocal(LensContext context, Task task ObjectDelta aPrioriDelta = getAPrioriDelta(context, projectionContext); if (!projectionContext.isDoReconciliation() && aPrioriDelta == null && !LensUtil.hasDependentContext(context, projectionContext) && !projectionContext.isFullShadow()) { - LOGGER.trace("Skipping processing of inbound expressions for projection {}: no reconciliation and no a priori delta and no dependent context", rat); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Skipping processing of inbound expressions for projection {}: no reconciliation and no a priori delta and no dependent context", + projectionContext.getHumanReadableName()); + } continue; } RefinedObjectClassDefinition rOcDef = projectionContext.getCompositeObjectClassDefinition(); if (rOcDef == null) { - LOGGER.error("Definition for projection type {} not found in the context, but it " + - "should be there, dumping context:\n{}", rat, context.debugDump()); - throw new IllegalStateException("Definition for projection type " + rat + LOGGER.error("Definition for projection {} not found in the context, but it " + + "should be there, dumping context:\n{}", projectionContext.getHumanReadableName(), context.debugDump()); + throw new IllegalStateException("Definition for projection " + projectionContext.getHumanReadableName() + " not found in the context, but it should be there"); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ReconciliationProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ReconciliationProcessor.java index f6a8ce2ae1c..8c160b6251b 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ReconciliationProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ReconciliationProcessor.java @@ -390,8 +390,10 @@ private void reconcileProjectionAttribute(QName attrName, // LOGGER.trace("Attribute reconciliation processing attribute {}",attrName); RefinedAttributeDefinition attributeDefinition = projCtx.findAttributeDefinition(attrName); if (attributeDefinition == null) { - throw new SchemaException("No definition for attribute " + attrName + " in " - + projCtx.getResourceShadowDiscriminator()); + String msg = "No definition for attribute " + attrName + " in " + + projCtx.getResourceShadowDiscriminator(); +// LOGGER.error("{}, structuralOC:\n{}", msg, projCtx.getStructuralObjectClassDefinition().debugDump()); + throw new SchemaException(msg); } DeltaSetTriple,PrismPropertyDefinition>> pvwoTriple = diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestMultiResource.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestMultiResource.java index c0fb8a57764..89bf8eddaa0 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestMultiResource.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestMultiResource.java @@ -62,6 +62,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.RoleType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; @@ -385,11 +386,13 @@ public void test209JackUnAssignDummyIvory() throws Exception { } /** - * Beige resource has a relaxed dependency. The provisioning should go OK. + * Beige resource has a relaxed dependency. The provisioning should go OK + * even if there is no default dummy account. */ @Test public void test210JackAssignDummyBeige() throws Exception { final String TEST_NAME = "test210JackAssignDummyBeige"; + TestUtil.displayTestTile(this, TEST_NAME); // GIVEN Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); @@ -400,9 +403,11 @@ public void test210JackAssignDummyBeige() throws Exception { modifyUserReplace(USER_JACK_OID, UserType.F_ORGANIZATIONAL_UNIT, task, result); // WHEN + TestUtil.displayWhen(TEST_NAME); assignAccount(USER_JACK_OID, RESOURCE_DUMMY_BEIGE_OID, null, task, result); // THEN + TestUtil.displayThen(TEST_NAME); result.computeStatus(); TestUtil.assertSuccess(result); @@ -419,11 +424,13 @@ public void test210JackAssignDummyBeige() throws Exception { } /** - * Beige resource has a relaxed dependency. The provisioning should go OK. + * Beige resource has a relaxed dependency. The deprovisioning should go OK + * even if there is not default dummy account. */ @Test public void test219JackUnAssignDummyBeige() throws Exception { final String TEST_NAME = "test219JackUnAssignDummyBeige"; + TestUtil.displayTestTile(this, TEST_NAME); // GIVEN Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); @@ -434,9 +441,11 @@ public void test219JackUnAssignDummyBeige() throws Exception { modifyUserReplace(USER_JACK_OID, UserType.F_ORGANIZATIONAL_UNIT, task, result); // WHEN + TestUtil.displayWhen(TEST_NAME); unassignAccount(USER_JACK_OID, RESOURCE_DUMMY_BEIGE_OID, null, task, result); // THEN + TestUtil.displayThen(TEST_NAME); result.computeStatus(); TestUtil.assertSuccess(result); @@ -449,6 +458,241 @@ public void test219JackUnAssignDummyBeige() throws Exception { assertNoDummyAccount(RESOURCE_DUMMY_BEIGE_NAME, ACCOUNT_JACK_DUMMY_USERNAME); } + /** + * Beige resource has a relaxed dependency. Try provisioning of both + * beige and default dummy accounts. + */ + @Test + public void test220JackAssignDummyBeigeAndDefault() throws Exception { + final String TEST_NAME = "test220JackAssignDummyBeigeAndDefault"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + assumeAssignmentPolicy(AssignmentPolicyEnforcementType.RELATIVE); + + // Clean up user + modifyUserReplace(USER_JACK_OID, UserType.F_ORGANIZATIONAL_UNIT, task, result); + + ObjectDelta userDelta = createAccountAssignmentUserDelta(USER_JACK_OID, RESOURCE_DUMMY_BEIGE_OID, null, true); + userDelta.addModification(createAssignmentModification(RESOURCE_DUMMY_OID, ShadowKindType.ACCOUNT, null, true)); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + executeChanges(userDelta, null, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userJack = getUser(USER_JACK_OID); + assertLinks(userJack, 2); + + assertDummyAccount(null, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); + assertDummyAccount(RESOURCE_DUMMY_BEIGE_NAME, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); + // No value for ship ... no place to get it from + assertDummyAccountAttribute(RESOURCE_DUMMY_BEIGE_NAME, ACCOUNT_JACK_DUMMY_USERNAME, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME); + + assertNoDummyAccount(RESOURCE_DUMMY_LAVENDER_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + assertNoDummyAccount(RESOURCE_DUMMY_IVORY_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + } + + /** + * Recompute to check that recompute will not ruin anything. + */ + @Test + public void test221JackRecompute() throws Exception { + final String TEST_NAME = "test221JackRecompute"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + recomputeUser(USER_JACK_OID, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userJack = getUser(USER_JACK_OID); + assertLinks(userJack, 2); + + assertDummyAccount(null, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); + assertDummyAccount(RESOURCE_DUMMY_BEIGE_NAME, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); + // No value for ship ... no place to get it from + assertDummyAccountAttribute(RESOURCE_DUMMY_BEIGE_NAME, ACCOUNT_JACK_DUMMY_USERNAME, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME); + + assertNoDummyAccount(RESOURCE_DUMMY_LAVENDER_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + assertNoDummyAccount(RESOURCE_DUMMY_IVORY_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + } + + /** + * Delete account on default dummy resource (but keep it assigned and keep the shadow). + * The recompute the user. The account should be re-created. + * MID-2134, MID-3093 + */ + @Test + public void test223JackKillDefaultDummyAccounAndRecompute() throws Exception { + final String TEST_NAME = "test223JackKillDefaultDummyAccounAndRecompute"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + dummyResource.deleteAccountByName(ACCOUNT_JACK_DUMMY_USERNAME); + display("dummy resource before", dummyResource); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + recomputeUser(USER_JACK_OID, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userJack = getUser(USER_JACK_OID); + display("user after", userJack); + assertLinks(userJack, 2); + + display("dummy resource after", dummyResource); + + assertDummyAccount(null, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); + assertDummyAccount(RESOURCE_DUMMY_BEIGE_NAME, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); + // No value for ship ... no place to get it from + assertDummyAccountAttribute(RESOURCE_DUMMY_BEIGE_NAME, ACCOUNT_JACK_DUMMY_USERNAME, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME); + + assertNoDummyAccount(RESOURCE_DUMMY_LAVENDER_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + assertNoDummyAccount(RESOURCE_DUMMY_IVORY_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + } + + /** + * Delete account on beige dummy resource (but keep it assigned and keep the shadow). + * The recompute the user. The account should be re-created. + * MID-2134, MID-3093 + */ + @Test + public void test224JackKillBeigeAccounAndRecompute() throws Exception { + final String TEST_NAME = "test224JackKillBeigeAccounAndRecompute"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + dummyResourceBeige.deleteAccountByName(ACCOUNT_JACK_DUMMY_USERNAME); + display("beige dummy resource before", dummyResourceBeige); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + recomputeUser(USER_JACK_OID, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userJack = getUser(USER_JACK_OID); + assertLinks(userJack, 2); + + display("beige dummy resource after", dummyResourceBeige); + + assertDummyAccount(null, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); + assertDummyAccount(RESOURCE_DUMMY_BEIGE_NAME, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); + // No value for ship ... no place to get it from + assertDummyAccountAttribute(RESOURCE_DUMMY_BEIGE_NAME, ACCOUNT_JACK_DUMMY_USERNAME, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME); + + assertNoDummyAccount(RESOURCE_DUMMY_LAVENDER_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + assertNoDummyAccount(RESOURCE_DUMMY_IVORY_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + } + + /** + * Delete both accounts on beige and default dummy resource (but keep it assigned and keep the shadows). + * The recompute the user. The accounts should be re-created. + * MID-2134, MID-3093 + */ + @Test + public void test225JackKillBothAccounsAndRecompute() throws Exception { + final String TEST_NAME = "test225JackKillBothAccounsAndRecompute"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + dummyResource.deleteAccountByName(ACCOUNT_JACK_DUMMY_USERNAME); + display("dummy resource before", dummyResource); + + dummyResourceBeige.deleteAccountByName(ACCOUNT_JACK_DUMMY_USERNAME); + display("beige dummy resource before", dummyResourceBeige); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + recomputeUser(USER_JACK_OID, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userJack = getUser(USER_JACK_OID); + assertLinks(userJack, 2); + + display("dummy resource after", dummyResource); + display("beige dummy resource after", dummyResourceBeige); + + assertDummyAccount(null, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); + assertDummyAccount(RESOURCE_DUMMY_BEIGE_NAME, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); + // No value for ship ... no place to get it from + assertDummyAccountAttribute(RESOURCE_DUMMY_BEIGE_NAME, ACCOUNT_JACK_DUMMY_USERNAME, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME); + + assertNoDummyAccount(RESOURCE_DUMMY_LAVENDER_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + assertNoDummyAccount(RESOURCE_DUMMY_IVORY_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + } + + /** + * Beige resource has a relaxed dependency. Try provisioning of both + * beige and default dummy accounts. + */ + @Test + public void test229JackUnassignDummyBeigeAndDefault() throws Exception { + final String TEST_NAME = "test229JackUnassignDummyBeigeAndDefault"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + assumeAssignmentPolicy(AssignmentPolicyEnforcementType.RELATIVE); + + ObjectDelta userDelta = createAccountAssignmentUserDelta(USER_JACK_OID, RESOURCE_DUMMY_BEIGE_OID, null, false); + userDelta.addModification(createAssignmentModification(RESOURCE_DUMMY_OID, ShadowKindType.ACCOUNT, null, false)); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + executeChanges(userDelta, null, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userJack = getUser(USER_JACK_OID); + assertLinks(userJack, 0); + + assertNoDummyAccount(ACCOUNT_JACK_DUMMY_USERNAME); + assertNoDummyAccount(RESOURCE_DUMMY_LAVENDER_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + assertNoDummyAccount(RESOURCE_DUMMY_IVORY_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + assertNoDummyAccount(RESOURCE_DUMMY_BEIGE_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + } + + /** * Lavender resource has a strict dependency. The provisioning should fail. */ diff --git a/model/model-intest/src/test/resources/logback-test.xml b/model/model-intest/src/test/resources/logback-test.xml index 0e49b7555fe..8b56e37f51e 100644 --- a/model/model-intest/src/test/resources/logback-test.xml +++ b/model/model-intest/src/test/resources/logback-test.xml @@ -67,7 +67,7 @@ - + diff --git a/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java b/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java index 6a3214e4ef1..31a845962ae 100644 --- a/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java +++ b/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java @@ -69,6 +69,7 @@ import com.evolveum.midpoint.provisioning.api.ProvisioningService; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.ObjectDeltaOperation; import com.evolveum.midpoint.schema.ResultHandler; import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.constants.ObjectTypes; @@ -990,6 +991,11 @@ protected ObjectDelta createAssignmentDelta(Class ty return userDelta; } + protected Collection> executeChanges(ObjectDelta objectDelta, ModelExecuteOptions options, Task task, OperationResult result) throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, PolicyViolationException, SecurityViolationException { + Collection> deltas = MiscSchemaUtil.createCollection(objectDelta); + return modelService.executeChanges(deltas, options, task, result); + } + protected void assignAccount(String userOid, String resourceOid, String intent) throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, PolicyViolationException, SecurityViolationException { Task task = taskManager.createTaskInstance(AbstractModelIntegrationTest.class+".assignAccount"); OperationResult result = task.getResult(); diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/consistency/impl/ObjectNotFoundHandler.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/consistency/impl/ObjectNotFoundHandler.java index 8023077a094..621850f84a1 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/consistency/impl/ObjectNotFoundHandler.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/consistency/impl/ObjectNotFoundHandler.java @@ -61,14 +61,12 @@ public class ObjectNotFoundHandler extends ErrorHandler { @Autowired @Qualifier("cacheRepositoryService") private RepositoryService cacheRepositoryService; -// @Autowired -// private ChangeNotificationDispatcher changeNotificationDispatcher; + @Autowired(required = true) private ProvisioningService provisioningService; + @Autowired(required = true) private TaskManager taskManager; - - private String oid = null; private static final Trace LOGGER = TraceManager.getTrace(ObjectNotFoundHandler.class); @@ -171,7 +169,7 @@ public T handleError(T shadow, FailedOperation op, Except } handleErrorResult.computeStatus(); String oidVal = null; - findReturnedValue(handleErrorResult, oidVal); + String oid = findCreatedAccountOid(handleErrorResult, oidVal); if (oid != null){ LOGGER.trace("Found new oid {} as a return param from model. Probably the new shadow was created.", oid); LOGGER.debug("DISCOVERY: object {} re-created, applying pending changes", shadow); @@ -212,7 +210,7 @@ public T handleError(T shadow, FailedOperation op, Except return shadow; case GET: if (!compensate){ - LOGGER.trace("DISCOVERY: cannot find object {}, GET operation: handling skipped", shadow); + LOGGER.trace("DISCOVERY: cannot find object {}, GET operation: handling skipped because discovery is disabled", shadow); result.recordFatalError(ex.getMessage(), ex); throw new ObjectNotFoundException(ex.getMessage(), ex); } @@ -248,15 +246,21 @@ public T handleError(T shadow, FailedOperation op, Except changeNotificationDispatcher.notifyChange(getChange, task, handleGetErrorResult); } catch (RuntimeException e) { + LOGGER.trace("DISCOVERY: synchronization invoked for {} ended with an error {}", shadow, e); handleGetErrorResult.recordFatalError(e); result.computeStatus(); throw e; } + // String oidVal = null; handleGetErrorResult.computeStatus(); - findReturnedValue(handleGetErrorResult, null); + + LOGGER.trace("DISCOVERY: synchronization invoked for {} finished with status {}", shadow, handleGetErrorResult.getStatus()); + + oid = findCreatedAccountOid(handleGetErrorResult, null); try { + LOGGER.trace("DISCOVERY: deleting {}", shadow); cacheRepositoryService.deleteObject(ShadowType.class, shadow.getOid(), result); } catch (ObjectNotFoundException e) { // delete the old shadow that was probably deleted from the @@ -270,18 +274,20 @@ public T handleError(T shadow, FailedOperation op, Except if (oid != null) { PrismObject newShadow; try { + LOGGER.trace("DISCOVERY: retrieving shadow {}", oid); newShadow = provisioningService.getObject(shadow.getClass(), oid, null, task, result); + LOGGER.trace("DISCOVERY: retrieved {}", newShadow); } finally { result.computeStatus(); } - LOGGER.debug("DISCOVERY: object {} re-created as {}. GET operation handler done.", shadow, newShadow); + LOGGER.debug("DISCOVERY: object {} re-created as {}. GET operation handler done: returning new shadow", shadow, newShadow); shadow = (T) newShadow.asObjectable(); parentResult.recordHandledError("Object was re-created by the discovery."); return shadow; } else { parentResult.recordHandledError("Object was deleted by the discovery and the invalid link was removed from the user."); result.computeStatus(); - LOGGER.debug("DISCOVERY: object {} was deleted. GET operation handler done.", shadow); + LOGGER.debug("DISCOVERY: object {} was deleted. GET operation handler done: throwing ObjectNotFoundException", shadow); throw new ObjectNotFoundException(ex.getMessage(), ex); } @@ -307,17 +313,19 @@ private ResourceObjectShadowChangeDescription createResourceObjectShadowChangeDe return change; } - private void findReturnedValue(OperationResult handleErrorResult, String oidVal) { + private String findCreatedAccountOid(OperationResult handleErrorResult, String oidVal) { if (oidVal != null) { - oid = oidVal; - return; + return oidVal; } List subresults = handleErrorResult.getSubresults(); for (OperationResult subresult : subresults) { String oidValue = (String) subresult.getReturn("createdAccountOid"); - findReturnedValue(subresult, oidValue); + String oid = findCreatedAccountOid(subresult, oidValue); + if (oid != null) { + return oid; + } } - return; + return null; } } diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/ConnectorInstanceIcfImpl.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/ConnectorInstanceIcfImpl.java index 3df0192f796..0fe91f09e29 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/ConnectorInstanceIcfImpl.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/ConnectorInstanceIcfImpl.java @@ -1162,17 +1162,17 @@ public PrismObject fetchObject(Class type, Resource if (icfConnectorFacade == null) { result.recordFatalError("Attempt to use unconfigured connector"); throw new IllegalStateException("Attempt to use unconfigured connector " - + ObjectTypeUtil.toShortString(connectorType)); + + ObjectTypeUtil.toShortString(connectorType) + " " + description); } // Get UID from the set of identifiers Uid uid = getUid(objectClassDefinition, identifiers); if (uid == null) { result.recordFatalError("Required attribute UID not found in identification set while attempting to fetch object identified by " - + identifiers + " from " + ObjectTypeUtil.toShortString(connectorType)); + + identifiers + " from " + description); throw new IllegalArgumentException( "Required attribute UID not found in identification set while attempting to fetch object identified by " - + identifiers + " from " + ObjectTypeUtil.toShortString(connectorType)); + + identifiers + " from " + description); } ObjectClass icfObjectClass = icfNameMapper.objectClassToIcf(objectClassDefinition, getSchemaNamespace(), connectorType, legacySchema); @@ -1180,11 +1180,11 @@ public PrismObject fetchObject(Class type, Resource result.recordFatalError("Unable to determine object class from QName " + objectClassDefinition.getTypeName() + " while attempting to fetch object identified by " + identifiers + " from " - + ObjectTypeUtil.toShortString(connectorType)); + + description); throw new IllegalArgumentException("Unable to determine object class from QName " + objectClassDefinition.getTypeName() + " while attempting to fetch object identified by " + identifiers + " from " - + ObjectTypeUtil.toShortString(connectorType)); + + description); } OperationOptionsBuilder optionsBuilder = new OperationOptionsBuilder(); @@ -1217,8 +1217,8 @@ public PrismObject fetchObject(Class type, Resource throw ex; } catch (ObjectNotFoundException ex) { result.recordFatalError("Object not found"); - throw new ObjectNotFoundException("Object identified by " + identifiers + " (ConnId UID "+uid+"), objectClass " + objectClassDefinition.getTypeName() + " was not found by " - + connectorType); + throw new ObjectNotFoundException("Object identified by " + identifiers + " (ConnId UID "+uid+"), objectClass " + objectClassDefinition.getTypeName() + " was not found in " + + description); } catch (SchemaException ex) { result.recordFatalError(ex); throw ex; @@ -1229,8 +1229,8 @@ public PrismObject fetchObject(Class type, Resource if (co == null) { result.recordFatalError("Object not found"); - throw new ObjectNotFoundException("Object identified by " + identifiers + " (ConnId UID "+uid+"), objectClass " + objectClassDefinition.getTypeName() + " was not found by " - + connectorType); + throw new ObjectNotFoundException("Object identified by " + identifiers + " (ConnId UID "+uid+"), objectClass " + objectClassDefinition.getTypeName() + " was not in " + + description); } PrismObjectDefinition shadowDefinition = toShadowDefinition(objectClassDefinition); From 849f2f8e6d6d6e72b127f93d40c52eac50259540 Mon Sep 17 00:00:00 2001 From: Radovan Semancik Date: Thu, 9 Jun 2016 10:26:53 +0200 Subject: [PATCH 2/2] Fixing last critical issues with consistency. Seems to work. (MID-3093) --- .../schema/ResourceShadowDiscriminator.java | 8 ++++++-- .../context/SynchronizationPolicyDecision.java | 11 +++++++---- .../model/impl/lens/LensProjectionContext.java | 5 ++++- .../impl/lens/projector/ContextLoader.java | 18 +++++++++++------- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/ResourceShadowDiscriminator.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/ResourceShadowDiscriminator.java index 19c2f597c8c..f19df4790c6 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/ResourceShadowDiscriminator.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/ResourceShadowDiscriminator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Evolveum + * Copyright (c) 2010-2016 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -133,7 +133,11 @@ public void setOrder(int order) { } /** - * Thumbstone flag is true: the account no longer exists. The data we have are the latest metadata we were able to get. + * Thumbstone flag is true: the account no longer exists. The data we have are the latest metadata we were able to get. + * The projection will be marked as thombstone if we discover that the associated resource object is gone. Or the shadow + * is gone and we can no longer associate the resource object. In any way the thombstoned projection is marked for removal. + * It will be eventually unlinked and the shadow will be deleted. The shadow may stay around in the "dead" state for + * some time for reporting purposes. */ public boolean isThombstone() { return thombstone; diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/SynchronizationPolicyDecision.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/SynchronizationPolicyDecision.java index 64aabb38516..8ac71a2ecef 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/SynchronizationPolicyDecision.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/SynchronizationPolicyDecision.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2013 Evolveum + * Copyright (c) 2010-2016 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,9 +47,12 @@ public enum SynchronizationPolicyDecision { UNLINK, /** - * The account is not usable. E.g. because the associated shadow does - * not exist any more, resource does not exists any more, etc. - * Such account link will be removed. + * The projection is broken. I.e. there is some (fixable) state that + * prevents proper operations with the projection. This may be schema violation + * problem, security problem (access denied), misconfiguration, etc. + * Broken projections will be kept in the state that they are (maintaining + * status quo) until the problem is fixed. We will do no operations on broken + * projections and we will NOT unlink them or delete them. */ BROKEN, diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java index da17f2d8427..6fbefcdeaed 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java @@ -545,6 +545,9 @@ public ResourceObjectTypeDefinitionType getResourceObjectTypeDefinitionType() { if (discr == null) { return null; // maybe when an account is deleted } + if (resource == null) { + return null; + } ResourceObjectTypeDefinitionType def = ResourceTypeUtil.getResourceObjectTypeDefinitionType(resource, discr.getKind(), discr.getIntent()); return def; } @@ -889,7 +892,7 @@ public void checkConsistence(String contextDesc, boolean fresh, boolean force) { if (synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN) { return; } - if (fresh && !force) { + if (fresh && !force && resourceShadowDiscriminator != null && !resourceShadowDiscriminator.isThombstone()) { if (resource == null) { throw new IllegalStateException("Null resource in "+this + (contextDesc == null ? "" : " in " +contextDesc)); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ContextLoader.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ContextLoader.java index 4fb2cb54bc0..b08b46ea72a 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ContextLoader.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ContextLoader.java @@ -1056,8 +1056,8 @@ private void finishLoadOfProjectionContext(LensContext resourceOid = ShadowUtil.getResourceOid(shadowType); } else if (projContext.getResourceShadowDiscriminator() != null) { resourceOid = projContext.getResourceShadowDiscriminator().getResourceOid(); - } else { - throw new IllegalStateException("No shadow and no resource intent means no resource OID in "+projectionHumanReadableName); + } else if (!thombstone) { + throw new IllegalStateException("No shadow, no discriminator and not thombstone? That won't do. Projection "+projectionHumanReadableName); } } else { resourceOid = resourceType.getOid(); @@ -1066,10 +1066,14 @@ private void finishLoadOfProjectionContext(LensContext // Determine discriminator ResourceShadowDiscriminator discr = projContext.getResourceShadowDiscriminator(); if (discr == null) { - ShadowType accountShadowType = projectionObject.asObjectable(); - String intent = ShadowUtil.getIntent(accountShadowType); - ShadowKindType kind = ShadowUtil.getKind(accountShadowType); - discr = new ResourceShadowDiscriminator(resourceOid, kind, intent, thombstone); + if (projectionObject != null) { + ShadowType accountShadowType = projectionObject.asObjectable(); + String intent = ShadowUtil.getIntent(accountShadowType); + ShadowKindType kind = ShadowUtil.getKind(accountShadowType); + discr = new ResourceShadowDiscriminator(resourceOid, kind, intent, thombstone); + } else { + discr = new ResourceShadowDiscriminator(null, null, null, thombstone); + } projContext.setResourceShadowDiscriminator(discr); } else { if (thombstone) { @@ -1079,7 +1083,7 @@ private void finishLoadOfProjectionContext(LensContext } // Load resource - if (resourceType == null) { + if (resourceType == null && resourceOid != null) { resourceType = LensUtil.getResource(context, resourceOid, provisioningService, task, result); projContext.setResource(resourceType); }