/* * Copyright (c) 2010-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.assertFalse; import static org.testng.AssertJUnit.assertNotNull; import static org.testng.AssertJUnit.assertNull; import java.io.File; import java.util.List; 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.icf.dummy.resource.DummyResource; import com.evolveum.midpoint.model.intest.rbac.TestRbac; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.statistics.ConnectorOperationalStatus; import com.evolveum.midpoint.schema.util.ShadowUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.test.DummyResourceContoller; import com.evolveum.midpoint.test.util.TestUtil; import com.evolveum.midpoint.util.FailableRunnable; import com.evolveum.midpoint.util.Holder; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; /** * Test behavior of connectors that have several instances (poolable connectors). * * @author semancik * */ @ContextConfiguration(locations = {"classpath:ctx-model-intest-test-main.xml"}) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) public class TestConnectorMultiInstance extends AbstractConfiguredModelIntegrationTest { public static final File TEST_DIR = new File("src/test/resources/multi"); private static Trace LOGGER = TraceManager.getTrace(TestConnectorMultiInstance.class); protected DummyResource dummyResourceYellow; protected DummyResourceContoller dummyResourceCtlYellow; private String accountJackYellowOid; private String initialConnectorStaticVal; private String initialConnectorToString; private String accountGuybrushBlackOid; @Override public void initSystem(Task initTask, OperationResult initResult) throws Exception { super.initSystem(initTask, initResult); dummyResourceCtlYellow = initDummyResourcePirate(RESOURCE_DUMMY_YELLOW_NAME, RESOURCE_DUMMY_YELLOW_FILE, RESOURCE_DUMMY_YELLOW_OID, initTask, initResult); dummyResourceYellow = dummyResourceCtlYellow.getDummyResource(); initDummyResourcePirate(RESOURCE_DUMMY_BLACK_NAME, RESOURCE_DUMMY_BLACK_FILE, RESOURCE_DUMMY_BLACK_OID, initTask, initResult); repoAddObjectFromFile(SECURITY_POLICY_FILE, initResult); repoAddObjectFromFile(USER_JACK_FILE, true, initResult); repoAddObjectFromFile(USER_GUYBRUSH_FILE, true, initResult); repoAddObjectFromFile(USER_ELAINE_FILE, true, initResult); } @Test public void test100JackAssignDummyYellow() throws Exception { final String TEST_NAME = "test100JackAssignDummyYellow"; TestUtil.displayTestTile(this, TEST_NAME); // GIVEN Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); // WHEN assignAccount(USER_JACK_OID, RESOURCE_DUMMY_YELLOW_OID, null, task, result); // THEN result.computeStatus(); TestUtil.assertSuccess(result); PrismObject<UserType> userJack = getUser(USER_JACK_OID); accountJackYellowOid = getSingleLinkOid(userJack); assertDummyAccount(RESOURCE_DUMMY_YELLOW_NAME, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); PrismObject<ShadowType> shadowYellow = getShadowModel(accountJackYellowOid); display("Shadow yellow", shadowYellow); assertConnectorInstances("yellow", RESOURCE_DUMMY_YELLOW_OID, 0, 1); initialConnectorToString = getConnectorToString(shadowYellow, dummyResourceCtlYellow); initialConnectorStaticVal = getConnectorStaticVal(shadowYellow, dummyResourceCtlYellow); } /** * This is sequential operation. Same connector instance should be reused. */ @Test public void test102ReadJackDummyYellowAgain() throws Exception { final String TEST_NAME = "test102ReadJackDummyYellowAgain"; TestUtil.displayTestTile(this, TEST_NAME); // WHEN PrismObject<ShadowType> shadowYellow = getShadowModel(accountJackYellowOid); // THEN display("Shadow yellow", shadowYellow); assertConnectorInstances("yellow", RESOURCE_DUMMY_YELLOW_OID, 0, 1); assertConnectorToString(shadowYellow, dummyResourceCtlYellow, initialConnectorToString); assertConnectorStaticVal(shadowYellow, dummyResourceCtlYellow, initialConnectorStaticVal); assertDummyAccount(RESOURCE_DUMMY_YELLOW_NAME, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); } /** * Block the operation during read. Just to make sure that the stats for active * connector instances work. */ @Test public void test110ReadJackDummyYellowBlocking() throws Exception { final String TEST_NAME = "test110ReadJackDummyYellowBlocking"; TestUtil.displayTestTile(this, TEST_NAME); dummyResourceYellow.setBlockOperations(true); final Holder<PrismObject<ShadowType>> shadowHolder = new Holder<>(); // WHEN Thread t = executeInNewThread("get1", new FailableRunnable() { @Override public void run() throws Exception { PrismObject<ShadowType> shadow = getShadowModel(accountJackYellowOid); LOGGER.trace("Got shadow {}", shadow); shadowHolder.setValue(shadow); } }); // Give the new thread a chance to get blocked Thread.sleep(200); assertConnectorInstances("yellow (blocked)", RESOURCE_DUMMY_YELLOW_OID, 1, 0); assertNull("Unexpected shadow", shadowHolder.getValue()); dummyResourceYellow.unblock(); // THEN t.join(); dummyResourceYellow.setBlockOperations(false); PrismObject<ShadowType> shadowYellow = shadowHolder.getValue(); assertNotNull("No shadow", shadowHolder.getValue()); display("Shadow yellow", shadowYellow); assertConnectorInstances("yellow", RESOURCE_DUMMY_YELLOW_OID, 0, 1); assertConnectorToString(shadowYellow, dummyResourceCtlYellow, initialConnectorToString); assertConnectorStaticVal(shadowYellow, dummyResourceCtlYellow, initialConnectorStaticVal); assertDummyAccount(RESOURCE_DUMMY_YELLOW_NAME, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); } /** * Block one read operation and let go the other. Make sure that new connector instance is created * for the second operation and that it goes smoothly. */ @Test public void test120ReadJackDummyYellowTwoOperationsOneBlocking() throws Exception { final String TEST_NAME = "test120ReadJackDummyYellowTwoOperationsOneBlocking"; TestUtil.displayTestTile(this, TEST_NAME); dummyResourceYellow.setBlockOperations(true); final Holder<PrismObject<ShadowType>> shadowHolder1 = new Holder<>(); final Holder<PrismObject<ShadowType>> shadowHolder2 = new Holder<>(); // WHEN Thread t1 = executeInNewThread("get1", new FailableRunnable() { @Override public void run() throws Exception { PrismObject<ShadowType> shadow = getShadowModel(accountJackYellowOid); LOGGER.trace("Got shadow {}", shadow); shadowHolder1.setValue(shadow); } }); // Give the new thread a chance to get blocked Thread.sleep(200); assertConnectorInstances("yellow (blocked)", RESOURCE_DUMMY_YELLOW_OID, 1, 0); assertNull("Unexpected shadow 1", shadowHolder1.getValue()); dummyResourceYellow.setBlockOperations(false); // This should not be blocked and it should proceed immediately Thread t2 = executeInNewThread("get2", new FailableRunnable() { @Override public void run() throws Exception { PrismObject<ShadowType> shadow = getShadowModel(accountJackYellowOid); LOGGER.trace("Got shadow {}", shadow); shadowHolder2.setValue(shadow); } }); t2.join(1000); assertConnectorInstances("yellow (blocked)", RESOURCE_DUMMY_YELLOW_OID, 1, 1); assertNull("Unexpected shadow 1", shadowHolder1.getValue()); dummyResourceYellow.unblock(); t1.join(); // THEN PrismObject<ShadowType> shadowYellow1 = shadowHolder1.getValue(); assertNotNull("No shadow 1", shadowHolder1.getValue()); display("Shadow yellow 1", shadowYellow1); PrismObject<ShadowType> shadowYellow2 = shadowHolder2.getValue(); assertNotNull("No shadow 2", shadowHolder2.getValue()); display("Shadow yellow 2", shadowYellow2); assertConnectorInstances("yellow", RESOURCE_DUMMY_YELLOW_OID, 0, 2); assertConnectorToString(shadowYellow1, dummyResourceCtlYellow, initialConnectorToString); assertConnectorStaticVal(shadowYellow1, dummyResourceCtlYellow, initialConnectorStaticVal); assertConnectorToStringDifferent(shadowYellow2, dummyResourceCtlYellow, initialConnectorToString); assertConnectorStaticVal(shadowYellow2, dummyResourceCtlYellow, initialConnectorStaticVal); assertDummyAccount(RESOURCE_DUMMY_YELLOW_NAME, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); } /** * Block two read operations. Make sure that new connector instance is created. */ @Test public void test125ReadJackDummyYellowTwoBlocking() throws Exception { final String TEST_NAME = "test125ReadJackDummyYellowTwoBlocking"; TestUtil.displayTestTile(this, TEST_NAME); dummyResourceYellow.setBlockOperations(true); final Holder<PrismObject<ShadowType>> shadowHolder1 = new Holder<>(); final Holder<PrismObject<ShadowType>> shadowHolder2 = new Holder<>(); // WHEN Thread t1 = executeInNewThread("get1", new FailableRunnable() { @Override public void run() throws Exception { PrismObject<ShadowType> shadow = getShadowModel(accountJackYellowOid); LOGGER.trace("Got shadow {}", shadow); shadowHolder1.setValue(shadow); } }); Thread t2 = executeInNewThread("get2", new FailableRunnable() { @Override public void run() throws Exception { PrismObject<ShadowType> shadow = getShadowModel(accountJackYellowOid); LOGGER.trace("Got shadow {}", shadow); shadowHolder2.setValue(shadow); } }); // Give the new threads a chance to get blocked Thread.sleep(500); assertConnectorInstances("yellow (blocked)", RESOURCE_DUMMY_YELLOW_OID, 2, 0); assertNull("Unexpected shadow 1", shadowHolder1.getValue()); assertNull("Unexpected shadow 2", shadowHolder2.getValue()); dummyResourceYellow.unblockAll(); t1.join(); t2.join(); // THEN dummyResourceYellow.setBlockOperations(false); PrismObject<ShadowType> shadowYellow1 = shadowHolder1.getValue(); assertNotNull("No shadow 1", shadowHolder1.getValue()); display("Shadow yellow 1", shadowYellow1); PrismObject<ShadowType> shadowYellow2 = shadowHolder2.getValue(); assertNotNull("No shadow 2", shadowHolder2.getValue()); display("Shadow yellow 2", shadowYellow2); assertConnectorInstances("yellow", RESOURCE_DUMMY_YELLOW_OID, 0, 2); assertConnectorToStringDifferent(shadowYellow2, dummyResourceCtlYellow, getConnectorToString(shadowYellow1, dummyResourceCtlYellow)); assertConnectorStaticVal(shadowYellow1, dummyResourceCtlYellow, initialConnectorStaticVal); assertConnectorStaticVal(shadowYellow2, dummyResourceCtlYellow, initialConnectorStaticVal); assertDummyAccount(RESOURCE_DUMMY_YELLOW_NAME, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); } @Test public void test200GuybrushAssignDummyBlack() throws Exception { final String TEST_NAME = "test200GuybrushAssignDummyBlack"; TestUtil.displayTestTile(this, TEST_NAME); // GIVEN Task task = taskManager.createTaskInstance(TestRbac.class.getName() + "." + TEST_NAME); OperationResult result = task.getResult(); // WHEN assignAccount(USER_GUYBRUSH_OID, RESOURCE_DUMMY_BLACK_OID, null, task, result); // THEN result.computeStatus(); TestUtil.assertSuccess(result); PrismObject<UserType> userJack = getUser(USER_GUYBRUSH_OID); accountGuybrushBlackOid = getSingleLinkOid(userJack); assertDummyAccount(RESOURCE_DUMMY_BLACK_NAME, ACCOUNT_GUYBRUSH_DUMMY_USERNAME, ACCOUNT_GUYBRUSH_DUMMY_FULLNAME, true); PrismObject<ShadowType> shadowBlack = getShadowModel(accountGuybrushBlackOid); display("Shadow black", shadowBlack); assertConnectorInstances("black", RESOURCE_DUMMY_BLACK_OID, 0, 1); assertConnectorInstances("yellow", RESOURCE_DUMMY_YELLOW_OID, 0, 2); assertConnectorToStringDifferent(shadowBlack, getDummyResourceController(RESOURCE_DUMMY_BLACK_NAME), initialConnectorStaticVal); assertConnectorToStringDifferent(shadowBlack, getDummyResourceController(RESOURCE_DUMMY_BLACK_NAME), initialConnectorToString); assertConnectorStaticVal(shadowBlack, getDummyResourceController(RESOURCE_DUMMY_BLACK_NAME), initialConnectorStaticVal); } private Thread executeInNewThread(final String threadName, final FailableRunnable runnable) { Thread t = new Thread(new Runnable() { @Override public void run() { try { login(userAdministrator); runnable.run(); } catch (Throwable e) { LOGGER.error("Error in {}: {}", threadName, e.getMessage(), e); } } }); t.setName(threadName); t.start(); return t; } private String getConnectorToString(PrismObject<ShadowType> shadow, DummyResourceContoller ctl) throws SchemaException { return ShadowUtil.getAttributeValue(shadow, ctl.getAttributeQName(DummyResource.ATTRIBUTE_CONNECTOR_TO_STRING)); } private String getConnectorStaticVal(PrismObject<ShadowType> shadow, DummyResourceContoller ctl) throws SchemaException { return ShadowUtil.getAttributeValue(shadow, ctl.getAttributeQName(DummyResource.ATTRIBUTE_CONNECTOR_STATIC_VAL)); } private void assertConnectorToString(PrismObject<ShadowType> shadow, DummyResourceContoller ctl, String expectedVal) throws SchemaException { String connectorVal = ShadowUtil.getAttributeValue(shadow, ctl.getAttributeQName(DummyResource.ATTRIBUTE_CONNECTOR_TO_STRING)); assertEquals("Connector toString mismatch", expectedVal, connectorVal); } private void assertConnectorToStringDifferent(PrismObject<ShadowType> shadow, DummyResourceContoller ctl, String expectedVal) throws SchemaException { String connectorVal = getConnectorToString(shadow, ctl); assertFalse("Unexpected Connector toString, expected a different value: "+connectorVal, expectedVal.equals(connectorVal)); } private void assertConnectorStaticVal(PrismObject<ShadowType> shadow, DummyResourceContoller ctl, String expectedVal) throws SchemaException { String connectorStaticVal = getConnectorStaticVal(shadow, ctl); assertEquals("Connector static val mismatch", expectedVal, connectorStaticVal); } private void assertConnectorInstances(String msg, String resourceOid, int expectedActive, int expectedIdle) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException { OperationResult result = new OperationResult(TestConnectorMultiInstance.class.getName() + ".assertConnectorInstances"); List<ConnectorOperationalStatus> opStats = modelInteractionService.getConnectorOperationalStatus(resourceOid, result); display("connector stats "+msg, opStats); assertConnectorInstances(msg, opStats.get(0), expectedActive, expectedIdle); } private void assertConnectorInstances(String msg, ConnectorOperationalStatus opStats, int expectedActive, int expectedIdle) { assertEquals(msg+" unexpected number of active connector instances", (Integer)expectedActive, opStats.getPoolStatusNumActive()); assertEquals(msg+" unexpected number of idle connector instances", (Integer)expectedIdle, opStats.getPoolStatusNumIdle()); } }