Skip to content

Commit

Permalink
Reclaiming unused sequence values on clockwork failure (+tests)
Browse files Browse the repository at this point in the history
  • Loading branch information
semancik committed Sep 29, 2015
1 parent 7878570 commit 57d557a
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 22 deletions.
Expand Up @@ -1227,17 +1227,6 @@ public ObjectDeltaType getResourceDelta(ModelContext context, String resourceOid
}

public long getSequenceCounter(String sequenceOid) throws ObjectNotFoundException, SchemaException {
LensContext<? extends FocusType> ctx = ModelExpressionThreadLocalHolder.getLensContext();
if (ctx == null) {
throw new IllegalStateException("No lens context");
}

Long counter = ctx.getSequenceCounter(sequenceOid);
if (counter == null) {
counter = repositoryService.advanceSequence(sequenceOid, getCurrentResult());
ctx.setSequenceCounter(sequenceOid, counter);
}

return counter;
return SequentialValueExpressionEvaluator.getSequenceCounter(sequenceOid, repositoryService, getCurrentResult());
}
}
Expand Up @@ -18,6 +18,7 @@
import com.evolveum.midpoint.model.common.expression.ExpressionEvaluationContext;
import com.evolveum.midpoint.model.common.expression.ExpressionEvaluator;
import com.evolveum.midpoint.model.common.expression.ExpressionUtil;
import com.evolveum.midpoint.model.impl.lens.LensContext;
import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.PrismContext;
Expand All @@ -28,9 +29,11 @@
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SequentialValueExpressionEvaluatorType;

/**
Expand All @@ -57,8 +60,7 @@ public class SequentialValueExpressionEvaluator<V extends PrismValue, D extends
@Override
public PrismValueDeltaSetTriple<V> evaluate(ExpressionEvaluationContext params) throws SchemaException,
ExpressionEvaluationException, ObjectNotFoundException {

long counter = repositoryService.advanceSequence(sequentialValueEvaluatorType.getSequenceRef().getOid(), params.getResult());
long counter = getSequenceCounter(sequentialValueEvaluatorType.getSequenceRef().getOid(), repositoryService, params.getResult());

Object value = ExpressionUtil.convertToOutputValue(counter, outputDefinition, protector);

Expand All @@ -72,6 +74,21 @@ public PrismValueDeltaSetTriple<V> evaluate(ExpressionEvaluationContext params)

return ItemDelta.toDeltaSetTriple(output, null);
}

public static long getSequenceCounter(String sequenceOid, RepositoryService repositoryService, OperationResult result) throws ObjectNotFoundException, SchemaException {
LensContext<? extends FocusType> ctx = ModelExpressionThreadLocalHolder.getLensContext();
if (ctx == null) {
throw new IllegalStateException("No lens context");
}

Long counter = ctx.getSequenceCounter(sequenceOid);
if (counter == null) {
counter = repositoryService.advanceSequence(sequenceOid, result);
ctx.setSequenceCounter(sequenceOid, counter);
}

return counter;
}

/* (non-Javadoc)
* @see com.evolveum.midpoint.common.expression.ExpressionEvaluator#shortDebugDump()
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2013 Evolveum
* Copyright (c) 2010-2015 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 @@ -112,6 +112,9 @@

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
* @author semancik
Expand Down Expand Up @@ -673,8 +676,10 @@ private <F extends ObjectType> void auditFinalExecution(LensContext<F> context,
}

private <F extends ObjectType> void processClockworkException(LensContext<F> context, Exception e, Task task, OperationResult result) throws SchemaException {
LOGGER.trace("Processing clockwork exception {}", e.toString());
result.recordFatalError(e);
auditEvent(context, AuditEventStage.EXECUTION, null, true, task, result);
reclaimSequences(context, task, result);
}

private <F extends ObjectType> void auditEvent(LensContext<F> context, AuditEventStage stage,
Expand Down Expand Up @@ -1022,5 +1027,22 @@ private <F extends FocusType> void authorizeAssignmentRequest(String actionUrl,
}
}

private <F extends ObjectType> void reclaimSequences(LensContext<F> context, Task task, OperationResult result) throws SchemaException {
Map<String, Long> sequenceMap = context.getSequences();
LOGGER.trace("Context sequence map: {}", sequenceMap);
for (Entry<String, Long> sequenceMapEntry: sequenceMap.entrySet()) {
Collection<Long> unusedValues = new ArrayList<>(1);
unusedValues.add(sequenceMapEntry.getValue());
try {
LOGGER.trace("Returning value {} to sequence {}", sequenceMapEntry.getValue(), sequenceMapEntry.getKey());
repositoryService.returnUnusedValuesToSequence(sequenceMapEntry.getKey(), unusedValues, result);
} catch (ObjectNotFoundException e) {
LOGGER.error("Cannot return unused value to sequence {}: it does not exist", sequenceMapEntry.getKey(), e);
// ... but otherwise ignore it and go on
}
}
}



}
Expand Up @@ -16,6 +16,7 @@
*/


import static org.testng.AssertJUnit.assertFalse;
import static com.evolveum.midpoint.test.IntegrationTestTools.display;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertNotNull;
Expand All @@ -34,6 +35,7 @@
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ContextConfiguration;
import org.testng.AssertJUnit;
import org.testng.annotations.AfterClass;
import org.testng.annotations.Test;

Expand Down Expand Up @@ -68,6 +70,7 @@
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
Expand All @@ -78,6 +81,7 @@
import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType;
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.SequenceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
Expand Down Expand Up @@ -134,6 +138,17 @@ public class TestUnix extends AbstractStoryTest {
private static final String USER_LARGO_FIST_NAME = "Largo";
private static final String USER_LARGO_LAST_NAME = "LaGrande";
private static final int USER_LARGO_UID_NUMBER = 1002;

private static final String USER_CAPSIZE_USERNAME = "capsize";
private static final String USER_CAPSIZE_FIST_NAME = "Kate";
private static final String USER_CAPSIZE_LAST_NAME = "Capsize";
private static final int USER_CAPSIZE_UID_NUMBER = 1003;

private static final String USER_WALLY_USERNAME = "wally";
private static final String USER_WALLY_FIST_NAME = "Wally";
private static final String USER_WALLY_LAST_NAME = "Feed";
private static final int USER_WALLY_UID_NUMBER = 1003;


private static final File STRUCT_LDIF_FILE = new File(TEST_DIR, "struct.ldif");

Expand All @@ -142,6 +157,9 @@ public class TestUnix extends AbstractStoryTest {
private static final String ROLE_VILLAINS_NAME = "villains";
private static final Integer ROLE_VILLAINS_GID = 999;

public static final File OBJECT_TEMPLATE_USER_FILE = new File(TEST_DIR, "object-template-user.xml");
public static final String OBJECT_TEMPLATE_USER_OID = "9cd03eda-66bd-11e5-866c-f3bc34108fdf";

public static final File SEQUENCE_UIDNUMBER_FILE = new File(TEST_DIR, "sequence-uidnumber.xml");
public static final String SEQUENCE_UIDNUMBER_OID = "7d4acb8c-65e3-11e5-9ef4-6382ba96fe6c";

Expand Down Expand Up @@ -179,14 +197,8 @@ public class TestUnix extends AbstractStoryTest {
private static final String ACCOUNT_STAN_FIST_NAME = "Stan";
private static final String ACCOUNT_STAN_LAST_NAME = "Salesman";

private static final String ACCOUNT_CAPSIZE_USERNAME = "capsize";
private static final String ACCOUNT_CAPSIZE_FIST_NAME = "Kate";
private static final String ACCOUNT_CAPSIZE_LAST_NAME = "Capsize";

private static final String ACCOUNT_WALLY_USERNAME = "wally";
private static final String ACCOUNT_WALLY_FIST_NAME = "Wally";
private static final String ACCOUNT_WALLY_LAST_NAME = "Feed";


private static final String ACCOUNT_AUGUSTUS_USERNAME = "augustus";
private static final String ACCOUNT_AUGUSTUS_FIST_NAME = "Augustus";
private static final String ACCOUNT_AUGUSTUS_LAST_NAME = "DeWaat";
Expand Down Expand Up @@ -239,6 +251,9 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti
// LDAP content
openDJController.addEntriesFromLdifFile(STRUCT_LDIF_FILE.getPath());

// Object Templates
importObjectFromFile(OBJECT_TEMPLATE_USER_FILE, initResult);
setDefaultUserTemplate(OBJECT_TEMPLATE_USER_OID);

// Role
importObjectFromFile(ROLE_BASIC_FILE, initResult);
Expand Down Expand Up @@ -751,6 +766,89 @@ public void test212AssignUserLargoVillains() throws Exception {
openDJController.assertAttribute(groupVillains, "memberUid", Integer.toString(USER_LARGO_UID_NUMBER));
}

@Test
public void test300AddUserCapsizeUnixFail() throws Exception {
final String TEST_NAME = "test300AddUserCapsizeUnixFail";
TestUtil.displayTestTile(this, TEST_NAME);
Task task = taskManager.createTaskInstance(TestUnix.class.getName() + "." + TEST_NAME);
OperationResult result = task.getResult();

PrismObject<SequenceType> sequenceBefore = getObject(SequenceType.class, SEQUENCE_UIDNUMBER_OID);
display("Sequence before", sequenceBefore);
assertEquals("Wrong sequence counter (precondition)", USER_CAPSIZE_UID_NUMBER, sequenceBefore.asObjectable().getCounter().intValue());
assertTrue("Unexpected unused values in the sequence (precondition)", sequenceBefore.asObjectable().getUnusedValues().isEmpty());

PrismObject<UserType> user = createUser(USER_CAPSIZE_USERNAME, USER_CAPSIZE_FIST_NAME, USER_CAPSIZE_LAST_NAME, ROLE_UNIX_OID);
user.asObjectable().getEmployeeType().add("troublemaker");

try {
// WHEN
TestUtil.displayWhen(TEST_NAME);
addObject(user, task, result);

AssertJUnit.fail("Unexpected success");
} catch (ExpressionEvaluationException e) {
display("Expected exception", e);
// this is expected
}

// THEN
TestUtil.displayThen(TEST_NAME);
result.computeStatus();
TestUtil.assertFailure(result);

PrismObject<UserType> userAfter = findUserByUsername(USER_CAPSIZE_USERNAME);
display("User after", userAfter);
assertNull("User capsize sneaked in", userAfter);

PrismObject<SequenceType> sequenceAfter = getObject(SequenceType.class, SEQUENCE_UIDNUMBER_OID);
display("Sequence after", sequenceAfter);
assertEquals("Sequence haven't moved", USER_CAPSIZE_UID_NUMBER + 1, sequenceAfter.asObjectable().getCounter().intValue());
assertFalse("No unused values in the sequence", sequenceAfter.asObjectable().getUnusedValues().isEmpty());
}

/**
* This should go well. It should reuse the identifier that was originally assigned to
* Kate Capsise, but not used.
*/
@Test
public void test310AddUserWallyUnix() throws Exception {
final String TEST_NAME = "test310AddUserWallyUnix";
TestUtil.displayTestTile(this, TEST_NAME);
Task task = taskManager.createTaskInstance(TestUnix.class.getName() + "." + TEST_NAME);
OperationResult result = task.getResult();

PrismObject<SequenceType> sequenceBefore = getObject(SequenceType.class, SEQUENCE_UIDNUMBER_OID);
display("Sequence before", sequenceBefore);
assertEquals("Wrong sequence counter (precondition)", USER_WALLY_UID_NUMBER + 1, sequenceBefore.asObjectable().getCounter().intValue());
assertFalse("Missing unused values in the sequence (precondition)", sequenceBefore.asObjectable().getUnusedValues().isEmpty());

PrismObject<UserType> user = createUser(USER_WALLY_USERNAME, USER_WALLY_FIST_NAME, USER_WALLY_LAST_NAME, ROLE_UNIX_OID);

// WHEN
TestUtil.displayWhen(TEST_NAME);
addObject(user, task, result);

// THEN
TestUtil.displayThen(TEST_NAME);
result.computeStatus();
TestUtil.assertSuccess(result);
PrismObject<UserType> userAfter = findUserByUsername(USER_WALLY_USERNAME);
assertNotNull("No herman user", userAfter);
display("User after", userAfter);
assertUserPosix(userAfter, USER_WALLY_USERNAME, USER_WALLY_FIST_NAME, USER_WALLY_LAST_NAME, USER_WALLY_UID_NUMBER);
accountMancombOid = getSingleLinkOid(userAfter);

PrismObject<ShadowType> shadow = getShadowModel(accountMancombOid);
display("Shadow (model)", shadow);
accountMancombDn = assertPosixAccount(shadow, USER_WALLY_UID_NUMBER);

PrismObject<SequenceType> sequenceAfter = getObject(SequenceType.class, SEQUENCE_UIDNUMBER_OID);
display("Sequence after", sequenceAfter);
assertEquals("Sequence has moved", USER_WALLY_UID_NUMBER + 1, sequenceAfter.asObjectable().getCounter().intValue());
assertTrue("Unexpected unused values in the sequence", sequenceAfter.asObjectable().getUnusedValues().isEmpty());
}

private PrismObject<UserType> createUser(String username, String givenName, String familyName, String roleOid) throws SchemaException {
PrismObject<UserType> user = createUser(username, givenName, familyName, true);
if (roleOid != null) {
Expand Down

0 comments on commit 57d557a

Please sign in to comment.