/*
* Copyright (c) 2016-2017 Evolveum
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.evolveum.midpoint.model.intest;
import static com.evolveum.midpoint.test.IntegrationTestTools.display;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertTrue;
import static org.testng.AssertJUnit.fail;
import java.io.File;
import java.util.*;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import javax.xml.datatype.XMLGregorianCalendar;
import com.evolveum.midpoint.audit.api.AuditEventStage;
import com.evolveum.midpoint.audit.api.AuditEventType;
import com.evolveum.midpoint.prism.delta.builder.DeltaBuilder;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.xml.XmlTypeConverter;
import com.evolveum.midpoint.schema.ObjectDeltaOperation;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ContextConfiguration;
import org.testng.annotations.Test;
import com.evolveum.midpoint.audit.api.AuditEventRecord;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.util.PrismAsserts;
import com.evolveum.midpoint.prism.util.PrismTestUtil;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.MiscSchemaUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.test.util.TestUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
/**
* Test of Model Audit Service.
*
* Two users, interlaced events: jack and herman.
*
* Jack already exists, but there is no create audit record for him. This simulates trimmed
* audit log. There are several fresh modify operations.
*
* Herman is properly created and modified. We have all the audit records.
*
* The tests check if audit records are created and that they can be listed.
* The other tests check that the state can be reconstructed by the time machine (object history).
*
* @author Radovan Semancik
*
*/
@ContextConfiguration(locations = {"classpath:ctx-model-intest-test-main.xml"})
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
public class TestAudit extends AbstractInitializedModelIntegrationTest {
public static final File TEST_DIR = new File("src/test/resources/audit");
public static final int INITIAL_NUMBER_OF_AUDIT_RECORDS = 26;
private XMLGregorianCalendar initialTs;
private XMLGregorianCalendar jackKidTs;
private String jackKidEid;
private XMLGregorianCalendar jackSailorTs;
private String jackSailorEid;
private XMLGregorianCalendar jackCaptainTs;
private String jackCaptainEid;
private XMLGregorianCalendar hermanInitialTs;
private XMLGregorianCalendar hermanCreatedTs;
private String hermanCreatedEid;
private XMLGregorianCalendar hermanMaroonedTs;
private String hermanMaroonedEid;
private XMLGregorianCalendar hermanHermitTs;
private String hermanHermitEid;
private XMLGregorianCalendar hermanCivilisedHermitTs;
private String hermanCivilisedHermitEid;
@Override
public void initSystem(Task initTask, OperationResult initResult) throws Exception {
super.initSystem(initTask, initResult);
}
@Test
public void test000Sanity() throws Exception {
final String TEST_NAME = "test000Sanity";
TestUtil.displayTestTile(this, TEST_NAME);
assertTrue(modelAuditService.supportsRetrieval());
// WHEN
List<AuditEventRecord> allRecords = modelAuditService.listRecords("from RAuditEventRecord as aer where 1=1 ",
new HashMap<>(), new OperationResult(TEST_NAME));
// THEN
display("all records", allRecords);
assertEquals("Wrong initial number of audit records", INITIAL_NUMBER_OF_AUDIT_RECORDS, allRecords.size());
}
@Test
public void test010SanityJack() throws Exception {
final String TEST_NAME = "test010SanityJack";
TestUtil.displayTestTile(this, TEST_NAME);
// WHEN
List<AuditEventRecord> auditRecords = getObjectAuditRecords(USER_JACK_OID);
// THEN
display("Jack records", auditRecords);
assertEquals("Wrong initial number of jack audit records", 0, auditRecords.size());
}
@Test
public void test100ModifyUserJackKid() throws Exception {
final String TEST_NAME = "test100ModifyUserJackKid";
TestUtil.displayTestTile(this, TEST_NAME);
Task task = taskManager.createTaskInstance(TestAudit.class.getName() + "." + TEST_NAME);
OperationResult result = task.getResult();
initialTs = getTimeSafely();
// WHEN
TestUtil.displayWhen(TEST_NAME);
modifyUserReplace(USER_JACK_OID, UserType.F_TITLE, task, result, createPolyString("Kid"));
// THEN
TestUtil.displayThen(TEST_NAME);
result.computeStatus();
TestUtil.assertSuccess(result);
jackKidTs = getTimeSafely();
jackKidEid = assertObjectAuditRecords(USER_JACK_OID, 2);
assertRecordsFromInitial(jackKidTs, 2);
}
/**
* Let's interlace the history of two objects. So we make sure that the filtering
* in the time machine works well.
*/
@Test
public void test105CreateUserHerman() throws Exception {
final String TEST_NAME = "test105CreateUserHerman";
TestUtil.displayTestTile(this, TEST_NAME);
Task task = taskManager.createTaskInstance(TestAudit.class.getName() + "." + TEST_NAME);
OperationResult result = task.getResult();
PrismObject<UserType> userHermanBefore = PrismTestUtil.parseObject(USER_HERMAN_FILE);
userHermanBefore.asObjectable().setDescription("Unknown");
userHermanBefore.asObjectable().setNickName(createPolyStringType("HT"));
hermanInitialTs = getTimeSafely();
// WHEN
TestUtil.displayWhen(TEST_NAME);
addObject(userHermanBefore, task, result);
// THEN
TestUtil.displayThen(TEST_NAME);
result.computeStatus();
TestUtil.assertSuccess(result);
PrismObject<UserType> user = getUser(USER_HERMAN_OID);
display("Herman (created)", user);
hermanCreatedTs = getTimeSafely();
hermanCreatedEid = assertObjectAuditRecords(USER_HERMAN_OID, 2);
assertRecordsFromInitial(hermanCreatedTs, 4);
}
@Test
public void test110ModifyUserJackSailor() throws Exception {
final String TEST_NAME = "test110ModifyUserJackSailor";
TestUtil.displayTestTile(this, TEST_NAME);
Task task = taskManager.createTaskInstance(TestAudit.class.getName() + "." + TEST_NAME);
OperationResult result = task.getResult();
ObjectDelta<UserType> objectDelta = createModifyUserReplaceDelta(USER_JACK_OID, UserType.F_TITLE,
createPolyString("Sailor"));
objectDelta.addModificationReplaceProperty(UserType.F_DESCRIPTION, "Hermit on Monkey Island");
objectDelta.addModificationReplaceProperty(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS,
ActivationStatusType.DISABLED);
// WHEN
TestUtil.displayWhen(TEST_NAME);
modelService.executeChanges(MiscSchemaUtil.createCollection(objectDelta), null, task, result);
// THEN
TestUtil.displayThen(TEST_NAME);
result.computeStatus();
TestUtil.assertSuccess(result);
jackSailorTs = getTimeSafely();
jackSailorEid = assertObjectAuditRecords(USER_JACK_OID, 4);
assertRecordsFromPrevious(hermanCreatedTs, jackSailorTs, 2);
assertRecordsFromPrevious(jackKidTs, jackSailorTs, 4);
assertRecordsFromInitial(jackSailorTs, 6);
}
@Test
public void test115ModifyUserHermanMarooned() throws Exception {
final String TEST_NAME = "test115ModifyUserHermanMarooned";
TestUtil.displayTestTile(this, TEST_NAME);
Task task = taskManager.createTaskInstance(TestAudit.class.getName() + "." + TEST_NAME);
OperationResult result = task.getResult();
ObjectDelta<UserType> objectDelta = createModifyUserReplaceDelta(USER_HERMAN_OID, UserType.F_TITLE,
createPolyString("Marooned"));
objectDelta.addModificationReplaceProperty(UserType.F_DESCRIPTION, "Marooned on Monkey Island");
objectDelta.addModification(createAssignmentModification(RESOURCE_DUMMY_OID, ShadowKindType.ACCOUNT, null, true));
// WHEN
TestUtil.displayWhen(TEST_NAME);
modelService.executeChanges(MiscSchemaUtil.createCollection(objectDelta), null, task, result);
// THEN
TestUtil.displayThen(TEST_NAME);
result.computeStatus();
TestUtil.assertSuccess(result);
PrismObject<UserType> user = getUser(USER_HERMAN_OID);
display("Herman (marooned)", user);
hermanMaroonedTs = getTimeSafely();
hermanMaroonedEid = assertObjectAuditRecords(USER_HERMAN_OID, 4);
assertRecordsFromInitial(hermanMaroonedTs, 8);
}
@Test
public void test120ModifyUserJackCaptain() throws Exception {
final String TEST_NAME = "test120ModifyUserJackCaptain";
TestUtil.displayTestTile(this, TEST_NAME);
Task task = taskManager.createTaskInstance(TestAudit.class.getName() + "." + TEST_NAME);
OperationResult result = task.getResult();
ObjectDelta<UserType> objectDelta = createModifyUserReplaceDelta(USER_JACK_OID, UserType.F_TITLE,
createPolyString("Captain"));
objectDelta.addModificationReplaceProperty(UserType.F_DESCRIPTION, "Hermit on Monkey Island");
objectDelta.addModificationReplaceProperty(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS,
ActivationStatusType.ENABLED);
// WHEN
TestUtil.displayWhen(TEST_NAME);
modelService.executeChanges(MiscSchemaUtil.createCollection(objectDelta), null, task, result);
// THEN
TestUtil.displayThen(TEST_NAME);
result.computeStatus();
TestUtil.assertSuccess(result);
jackCaptainTs = getTimeSafely();
jackCaptainEid = assertObjectAuditRecords(USER_JACK_OID, 6);
assertRecordsFromPrevious(hermanMaroonedTs, jackCaptainTs, 2);
assertRecordsFromPrevious(jackSailorTs, jackCaptainTs, 4);
assertRecordsFromInitial(jackCaptainTs, 10);
}
@Test
public void test125ModifyUserHermanHermit() throws Exception {
final String TEST_NAME = "test125ModifyUserHermanHermit";
TestUtil.displayTestTile(this, TEST_NAME);
Task task = taskManager.createTaskInstance(TestAudit.class.getName() + "." + TEST_NAME);
OperationResult result = task.getResult();
ObjectDelta<UserType> objectDelta = createModifyUserReplaceDelta(USER_HERMAN_OID, UserType.F_TITLE,
createPolyString("Hermit"));
objectDelta.addModificationReplaceProperty(UserType.F_DESCRIPTION, "Hermit on Monkey Island");
objectDelta.addModificationReplaceProperty(UserType.F_HONORIFIC_PREFIX, createPolyString("His Loneliness"));
objectDelta.addModificationReplaceProperty(UserType.F_NICK_NAME);
objectDelta.addModification(createAssignmentModification(RESOURCE_DUMMY_OID, ShadowKindType.ACCOUNT, null, false));
objectDelta.addModification(createAssignmentModification(ROLE_JUDGE_OID, RoleType.COMPLEX_TYPE,
null, null, null, true));
objectDelta.addModification(createAssignmentModification(ROLE_RED_SAILOR_OID, RoleType.COMPLEX_TYPE,
null, null, null, true));
// WHEN
TestUtil.displayWhen(TEST_NAME);
modelService.executeChanges(MiscSchemaUtil.createCollection(objectDelta), null, task, result);
// THEN
TestUtil.displayThen(TEST_NAME);
result.computeStatus();
TestUtil.assertSuccess(result);
PrismObject<UserType> user = getUser(USER_HERMAN_OID);
display("Herman (hermit)", user);
hermanHermitTs = getTimeSafely();
hermanHermitEid = assertObjectAuditRecords(USER_HERMAN_OID, 6);
assertRecordsFromInitial(hermanHermitTs, 12);
}
@Test
public void test135ModifyUserHermanCivilisedHermit() throws Exception {
final String TEST_NAME = "test135ModifyUserHermanCivilisedHermit";
TestUtil.displayTestTile(this, TEST_NAME);
Task task = taskManager.createTaskInstance(TestAudit.class.getName() + "." + TEST_NAME);
OperationResult result = task.getResult();
ObjectDelta<UserType> objectDelta = createModifyUserReplaceDelta(USER_HERMAN_OID, UserType.F_TITLE,
createPolyString("Civilised Hermit"));
objectDelta.addModificationReplaceProperty(UserType.F_DESCRIPTION, "Civilised Hermit on Monkey Island");
objectDelta.addModification(createAssignmentModification(ROLE_RED_SAILOR_OID, RoleType.COMPLEX_TYPE,
null, null, null, false));
// WHEN
TestUtil.displayWhen(TEST_NAME);
modelService.executeChanges(MiscSchemaUtil.createCollection(objectDelta), null, task, result);
// THEN
TestUtil.displayThen(TEST_NAME);
result.computeStatus();
TestUtil.assertSuccess(result);
PrismObject<UserType> user = getUser(USER_HERMAN_OID);
display("Herman (civilised hermit)", user);
hermanCivilisedHermitTs = getTimeSafely();
hermanCivilisedHermitEid = assertObjectAuditRecords(USER_HERMAN_OID, 8);
assertRecordsFromInitial(hermanCivilisedHermitTs, 14);
}
@Test
public void test200ReconstructJackSailor() throws Exception {
final String TEST_NAME = "test200ReconstructJackSailor";
TestUtil.displayTestTile(this, TEST_NAME);
Task task = taskManager.createTaskInstance(TestAudit.class.getName() + "." + TEST_NAME);
OperationResult result = task.getResult();
PrismObject<UserType> userBefore = getUser(USER_JACK_OID);
display("User before", userBefore);
// precondition
PrismAsserts.assertPropertyValue(userBefore, UserType.F_TITLE, createPolyString("Captain"));
// WHEN
TestUtil.displayWhen(TEST_NAME);
PrismObject<UserType> jackReconstructed = modelAuditService.reconstructObject(UserType.class, USER_JACK_OID,
jackSailorEid, task, result);
// THEN
TestUtil.displayThen(TEST_NAME);
result.computeStatus();
TestUtil.assertSuccess(result);
display("Reconstructed jack", jackReconstructed);
PrismAsserts.assertPropertyValue(jackReconstructed, UserType.F_TITLE, createPolyString("Sailor"));
assertAdministrativeStatusDisabled(jackReconstructed);
// TODO
}
@Test
public void test210ReconstructJackKid() throws Exception {
final String TEST_NAME = "test210ReconstructJackKid";
TestUtil.displayTestTile(this, TEST_NAME);
Task task = taskManager.createTaskInstance(TestAudit.class.getName() + "." + TEST_NAME);
OperationResult result = task.getResult();
PrismObject<UserType> userBefore = getUser(USER_JACK_OID);
display("User before", userBefore);
// precondition
PrismAsserts.assertPropertyValue(userBefore, UserType.F_TITLE, createPolyString("Captain"));
// WHEN
TestUtil.displayWhen(TEST_NAME);
PrismObject<UserType> jackReconstructed = modelAuditService.reconstructObject(UserType.class, USER_JACK_OID,
jackKidEid, task, result);
// THEN
TestUtil.displayThen(TEST_NAME);
result.computeStatus();
TestUtil.assertSuccess(result);
display("Reconstructed jack", jackReconstructed);
PrismAsserts.assertPropertyValue(jackReconstructed, UserType.F_TITLE, createPolyString("Kid"));
// TODO
}
/**
* This is supposed to get the objectToAdd directly from the create delta.
*/
@Test
public void test250ReconstructHermanCreated() throws Exception {
final String TEST_NAME = "test250ReconstructHermanCreated";
TestUtil.displayTestTile(this, TEST_NAME);
Task task = taskManager.createTaskInstance(TestAudit.class.getName() + "." + TEST_NAME);
OperationResult result = task.getResult();
PrismObject<UserType> userBefore = getUser(USER_HERMAN_OID);
display("User before", userBefore);
// precondition
PrismAsserts.assertPropertyValue(userBefore, UserType.F_TITLE, createPolyString("Civilised Hermit"));
PrismAsserts.assertPropertyValue(userBefore, UserType.F_HONORIFIC_PREFIX, createPolyString("His Loneliness"));
PrismAsserts.assertNoItem(userBefore, UserType.F_NICK_NAME);
assertAssignments(userBefore, 1);
// WHEN
TestUtil.displayWhen(TEST_NAME);
PrismObject<UserType> hermanReconstructed = modelAuditService.reconstructObject(UserType.class, USER_HERMAN_OID,
hermanCreatedEid, task, result);
// THEN
TestUtil.displayThen(TEST_NAME);
result.computeStatus();
TestUtil.assertSuccess(result);
display("Reconstructed herman", hermanReconstructed);
PrismAsserts.assertPropertyValue(hermanReconstructed, UserType.F_DESCRIPTION, "Unknown");
PrismAsserts.assertPropertyValue(hermanReconstructed, UserType.F_NICK_NAME, createPolyString("HT"));
PrismAsserts.assertNoItem(hermanReconstructed, UserType.F_TITLE);
PrismAsserts.assertNoItem(hermanReconstructed, UserType.F_HONORIFIC_PREFIX);
PrismAsserts.assertPropertyValue(hermanReconstructed, UserType.F_LOCALITY, createPolyString("Monkey Island"));
assertNoAssignments(hermanReconstructed);
}
/**
* Rolling back the deltas from the current state.
*/
@Test
public void test252ReconstructHermanMarooned() throws Exception {
final String TEST_NAME = "test252ReconstructHermanMarooned";
TestUtil.displayTestTile(this, TEST_NAME);
Task task = taskManager.createTaskInstance(TestAudit.class.getName() + "." + TEST_NAME);
OperationResult result = task.getResult();
// WHEN
TestUtil.displayWhen(TEST_NAME);
PrismObject<UserType> hermanReconstructed = modelAuditService.reconstructObject(UserType.class, USER_HERMAN_OID,
hermanMaroonedEid, task, result);
// THEN
TestUtil.displayThen(TEST_NAME);
result.computeStatus();
TestUtil.assertSuccess(result);
display("Reconstructed herman", hermanReconstructed);
PrismAsserts.assertPropertyValue(hermanReconstructed, UserType.F_TITLE, createPolyString("Marooned"));
PrismAsserts.assertPropertyValue(hermanReconstructed, UserType.F_DESCRIPTION, "Marooned on Monkey Island");
PrismAsserts.assertPropertyValue(hermanReconstructed, UserType.F_NICK_NAME, createPolyString("HT"));
PrismAsserts.assertPropertyValue(hermanReconstructed, UserType.F_LOCALITY, createPolyString("Monkey Island"));
PrismAsserts.assertNoItem(hermanReconstructed, UserType.F_HONORIFIC_PREFIX);
assertAssignedAccount(hermanReconstructed, RESOURCE_DUMMY_OID);
assertAssignments(hermanReconstructed, 1);
}
/**
* Rolling back the deltas from the current state.
*/
@Test
public void test254ReconstructHermanHermit() throws Exception {
final String TEST_NAME = "test254ReconstructHermanHermit";
TestUtil.displayTestTile(this, TEST_NAME);
Task task = taskManager.createTaskInstance(TestAudit.class.getName() + "." + TEST_NAME);
OperationResult result = task.getResult();
// WHEN
TestUtil.displayWhen(TEST_NAME);
PrismObject<UserType> hermanReconstructed = modelAuditService.reconstructObject(UserType.class, USER_HERMAN_OID,
hermanHermitEid, task, result);
// THEN
TestUtil.displayThen(TEST_NAME);
result.computeStatus();
TestUtil.assertSuccess(result);
display("Reconstructed herman", hermanReconstructed);
PrismAsserts.assertPropertyValue(hermanReconstructed, UserType.F_TITLE, createPolyString("Hermit"));
PrismAsserts.assertPropertyValue(hermanReconstructed, UserType.F_DESCRIPTION, "Hermit on Monkey Island");
PrismAsserts.assertNoItem(hermanReconstructed, UserType.F_NICK_NAME);
PrismAsserts.assertPropertyValue(hermanReconstructed, UserType.F_LOCALITY, createPolyString("Monkey Island"));
PrismAsserts.assertPropertyValue(hermanReconstructed, UserType.F_HONORIFIC_PREFIX, createPolyString("His Loneliness"));
assertAssignedRole(hermanReconstructed, ROLE_RED_SAILOR_OID);
assertAssignedRole(hermanReconstructed, ROLE_JUDGE_OID);
assertAssignments(hermanReconstructed, 2);
}
private String assertObjectAuditRecords(String oid, int expectedNumberOfRecords) throws SecurityViolationException, SchemaException {
List<AuditEventRecord> auditRecords = getObjectAuditRecords(oid);
display("Object records", auditRecords);
assertEquals("Wrong number of jack audit records", expectedNumberOfRecords, auditRecords.size());
return auditRecords.get(auditRecords.size() - 1).getEventIdentifier();
}
private void assertRecordsFromPrevious(XMLGregorianCalendar from, XMLGregorianCalendar to,
int expectedNumberOfRecords) throws SecurityViolationException, SchemaException {
List<AuditEventRecord> auditRecordsSincePrevious = getAuditRecordsFromTo(from, to);
display("From/to records (previous)", auditRecordsSincePrevious);
assertEquals("Wrong number of audit records (previous)", expectedNumberOfRecords, auditRecordsSincePrevious.size());
}
private void assertRecordsFromInitial(XMLGregorianCalendar to, int expectedNumberOfRecords) throws SecurityViolationException, SchemaException {
List<AuditEventRecord> auditRecordsSincePrevious = getAuditRecordsFromTo(initialTs, to);
display("From/to records (initial)", auditRecordsSincePrevious);
assertEquals("Wrong number of audit records (initial)", expectedNumberOfRecords, auditRecordsSincePrevious.size());
}
/**
* We make concurrent modify operations to test audit service under higher load
*/
@Test
public void test300ConcurrentAudits() throws Exception {
final String TEST_NAME = "test300ConcurrentAudits";
final int NUM_THREADS = 2;
final int ITERATIONS = 300;
final long TIMEOUT = 600000;
if (isH2()) {
display("Skipping " + TEST_NAME + " because of H2 database");
return;
}
// creating objects
List<String> oids = new ArrayList<>(NUM_THREADS);
for (int i = 0; i < NUM_THREADS; i++) {
UserType user = new UserType(prismContext);
user.setName(PolyStringType.fromOrig("user-" + i));
addObject(user.asPrismObject());
oids.add(user.getOid());
}
display("OIDs", oids);
// creating threads + starting them
List<Thread> threads = new ArrayList<>(NUM_THREADS);
List<Throwable> results = new ArrayList<>(NUM_THREADS);
for (int i = 0; i < NUM_THREADS; i++) {
final int index = i;
Thread thread = new Thread(() -> {
try {
login(userAdministrator);
Task threadTask = taskManager.createTaskInstance(TestAudit.class.getName() + "." + TEST_NAME);
OperationResult threadResult = threadTask.getResult();
for (int iteration = 0; iteration < ITERATIONS; iteration++) {
display("Executing iteration " + iteration + " on user " + index);
ObjectDelta delta = DeltaBuilder.deltaFor(UserType.class, prismContext)
.item(UserType.F_FULL_NAME).replace(PolyString.fromOrig("User " + index + " iteration " + iteration))
.asObjectDelta(oids.get(index));
executeChangesAssertSuccess(delta, null, threadTask, threadResult);
}
results.set(index, null);
} catch (Throwable t) {
System.err.println("Thread " + index + " got an exception " + t);
LoggingUtils.logUnexpectedException(LOGGER, "Thread {} got an exception", t, index);
results.set(index, t);
}
});
//thread.setName("Worker " + i);
threads.add(thread);
results.add(new IllegalStateException("Thread not finished")); // cleared on successful finish
}
threads.forEach(Thread::start);
// waiting for threads
long deadline = System.currentTimeMillis() + TIMEOUT;
for (int i = 0; i < NUM_THREADS; i++) {
long waitTime = deadline - System.currentTimeMillis();
if (waitTime > 0) {
threads.get(i).join(waitTime);
}
}
// checking results
int fails = 0;
for (int i = 0; i < NUM_THREADS; i++) {
if (results.get(i) != null) {
fails++;
display("Thread " + i + " produced an exception: " + results.get(i));
}
}
if (fails > 0) {
fail(fails + " thread(s) failed: " + results.stream().filter(Objects::nonNull).collect(Collectors.toList()));
}
// TODO check audit correctness
}
/**
* Pure audit attempts (TODO move to some other test class in lower levels)
*/
@Test
public void test310ConcurrentAuditsRaw() throws Exception {
final String TEST_NAME = "test310ConcurrentAuditsRaw";
final int NUM_THREADS = 2;
final int ITERATIONS = 300;
final long TIMEOUT = 600000;
if (isH2()) {
display("Skipping " + TEST_NAME + " because of H2 database");
return;
}
final AtomicBoolean failed = new AtomicBoolean(false); // signal to kill other threads after a failure
// creating threads + starting them
List<Thread> threads = new ArrayList<>(NUM_THREADS);
List<Throwable> results = new ArrayList<>(NUM_THREADS);
for (int i = 0; i < NUM_THREADS; i++) {
final int index = i;
Thread thread = new Thread(() -> {
try {
login(userAdministrator);
Task threadTask = taskManager.createTaskInstance(TestAudit.class.getName() + "." + TEST_NAME);
OperationResult threadResult = threadTask.getResult();
for (int iteration = 0; iteration < ITERATIONS; iteration++) {
display("Executing iteration " + iteration + " in worker " + index);
AuditEventRecord record = new AuditEventRecord(AuditEventType.MODIFY_OBJECT, AuditEventStage.EXECUTION);
record.setEventIdentifier(
String.valueOf(iteration + ":" + System.currentTimeMillis()) + "-" + (int) (Math.random() * 1000000));
ObjectDelta<?> delta = DeltaBuilder.deltaFor(UserType.class, prismContext)
.item(UserType.F_FULL_NAME).replace(PolyString.fromOrig("Hi" + iteration))
.item(UserType.F_METADATA, MetadataType.F_MODIFY_TIMESTAMP).replace(XmlTypeConverter.createXMLGregorianCalendar(new Date()))
.asObjectDelta("oid" + index);
record.getDeltas().add(new ObjectDeltaOperation(delta));
modelAuditService.audit(record, threadTask, threadResult);
if (failed.get()) {
results.set(index, new IllegalStateException("Some other thread failed"));
return;
}
}
results.set(index, null);
} catch (Throwable t) {
System.err.println("Thread " + index + " got an exception " + t);
LoggingUtils.logUnexpectedException(LOGGER, "Thread {} got an exception", t, index);
results.set(index, t);
failed.set(true);
}
});
thread.setName("Worker " + i);
threads.add(thread);
results.add(new IllegalStateException("Thread not finished")); // cleared on successful finish
}
threads.forEach(Thread::start);
// waiting for threads
long deadline = System.currentTimeMillis() + TIMEOUT;
for (int i = 0; i < NUM_THREADS; i++) {
long waitTime = deadline - System.currentTimeMillis();
if (waitTime > 0) {
threads.get(i).join(waitTime);
}
}
// checking results
int fails = 0;
for (int i = 0; i < NUM_THREADS; i++) {
if (results.get(i) != null) {
fails++;
display("Thread " + i + " produced an exception: " + results.get(i));
}
}
if (fails > 0) {
fail(fails + " thread(s) failed: " + results.stream().filter(Objects::nonNull).collect(Collectors.toList()));
}
// TODO check audit correctness
}
// If a timestamp is retrieved at the same time an operation started/finished, wrong results might be assumed
// (audit record could be mistakenly included in the time interval bounded by the timestamp).
// As I am currently too lazy to think out all possible failure modes, the safest way is to separate
// timestamp retrieval from any audited actions by small amount of time.
private XMLGregorianCalendar getTimeSafely() {
sleep(50);
XMLGregorianCalendar time = clock.currentTimeXMLGregorianCalendar();
sleep(50);
return time;
}
private void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
// no problem here
}
}
}