/* * RHQ Management Platform * Copyright (C) 2005-2013 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package org.rhq.enterprise.server.configuration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import org.jetbrains.annotations.NotNull; import org.testng.annotations.Test; import org.rhq.core.clientapi.agent.PluginContainerException; import org.rhq.core.clientapi.agent.configuration.ConfigurationAgentService; import org.rhq.core.clientapi.agent.configuration.ConfigurationUpdateRequest; import org.rhq.core.clientapi.agent.discovery.DiscoveryAgentService; import org.rhq.core.clientapi.agent.discovery.InvalidPluginConfigurationClientException; import org.rhq.core.clientapi.server.configuration.ConfigurationUpdateResponse; import org.rhq.core.clientapi.server.discovery.InventoryReport; import org.rhq.core.domain.auth.Subject; import org.rhq.core.domain.configuration.Configuration; import org.rhq.core.domain.configuration.ConfigurationUpdateStatus; import org.rhq.core.domain.configuration.PluginConfigurationUpdate; import org.rhq.core.domain.configuration.PropertySimple; import org.rhq.core.domain.configuration.ResourceConfigurationUpdate; import org.rhq.core.domain.configuration.definition.ConfigurationDefinition; import org.rhq.core.domain.configuration.definition.PropertyDefinition; import org.rhq.core.domain.configuration.definition.PropertyDefinitionSimple; import org.rhq.core.domain.configuration.definition.PropertySimpleType; import org.rhq.core.domain.configuration.group.GroupPluginConfigurationUpdate; import org.rhq.core.domain.criteria.ResourceConfigurationUpdateCriteria; import org.rhq.core.domain.discovery.AvailabilityReport; import org.rhq.core.domain.discovery.MergeResourceResponse; import org.rhq.core.domain.discovery.PlatformSyncInfo; import org.rhq.core.domain.discovery.ResourceSyncInfo; import org.rhq.core.domain.resource.Agent; import org.rhq.core.domain.resource.Resource; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.domain.resource.group.ResourceGroup; import org.rhq.core.domain.util.PageControl; import org.rhq.core.domain.util.PageOrdering; import org.rhq.core.util.exception.ThrowableUtil; import org.rhq.enterprise.server.authz.PermissionException; import org.rhq.enterprise.server.resource.ResourceManagerLocal; import org.rhq.enterprise.server.rest.BadArgumentException; import org.rhq.enterprise.server.test.AbstractEJB3Test; import org.rhq.enterprise.server.test.TestServerCommunicationsService; import org.rhq.enterprise.server.util.LookupUtil; import org.rhq.enterprise.server.util.SessionTestHelper; /** * Tests the configuration manager. */ @Test @SuppressWarnings("all") public class ConfigurationManagerBeanTest extends AbstractEJB3Test { private static final boolean ENABLE_TESTS = true; private ConfigurationManagerLocal configurationManager; private ResourceManagerLocal resourceManager; private Resource newResource1; private Resource newResource2; private ResourceGroup compatibleGroup; private PageControl configUpdatesPageControl; private Agent agent; private Subject overlord; @Override protected void beforeMethod() throws Exception { // Make sure page control sorts so the latest config update is last (the default is for the latest to be first). configUpdatesPageControl = PageControl.getUnlimitedInstance(); // (ips, 04/01/10): Use createdTime, rather than id, to order by, since the id's are not guaranteed to be // ordered sequentially (this is because, dbsetup configures our sequences to pre-create // and cache sequence values 10 at a time. configUpdatesPageControl.addDefaultOrderingField("cu.createdTime", PageOrdering.ASC); configurationManager = LookupUtil.getConfigurationManager(); resourceManager = LookupUtil.getResourceManager(); prepareScheduler(); TestServerCommunicationsService agentServiceContainer = prepareForTestAgents(); TestServices testServices = new TestServices(); agentServiceContainer.configurationService = testServices; agentServiceContainer.discoveryService = testServices; overlord = LookupUtil.getSubjectManager().getOverlord(); getTransactionManager().begin(); try { compatibleGroup = SessionTestHelper.createNewCompatibleGroupForRole(em, null, // no role necessary here "compat"); newResource1 = SessionTestHelper.createNewResourceForGroup(em, compatibleGroup, "res" + System.currentTimeMillis()); newResource2 = SessionTestHelper.createNewResourceForGroup(em, compatibleGroup, "res" + System.currentTimeMillis()); // set one resource as the child of another, so that they don't both look like platforms under the agent newResource1.addChildResource(newResource2); agent = SessionTestHelper.createNewAgent(em, "agent-" + getClass().getSimpleName()); newResource1.setAgent(agent); newResource2.setAgent(agent); getTransactionManager().commit(); } catch (Exception e) { try { System.out.println(e); getTransactionManager().rollback(); } catch (Exception ignore) { } throw e; } } @Override protected void afterMethod() throws Exception { try { // perform in-band and out-of-band work in quick succession // only need to delete newResource1, which will delete his child newResource2 as well as the agent List<Integer> deletedIds = resourceManager.uninventoryResource(overlord, newResource1.getId()); for (Integer deletedResourceId : deletedIds) { resourceManager.uninventoryResourceAsyncWork(overlord, deletedResourceId); } } catch (Exception e) { System.out.println(e); throw e; } try { getTransactionManager().begin(); try { ResourceGroup group = em.find(ResourceGroup.class, compatibleGroup.getId()); em.remove(group); getTransactionManager().commit(); } catch (Exception e) { try { System.out.println(e); getTransactionManager().rollback(); } catch (Exception ignore) { } throw e; } getTransactionManager().begin(); try { ResourceType type = em.find(ResourceType.class, newResource1.getResourceType().getId()); em.remove(type); getTransactionManager().commit(); } catch (Exception e) { try { System.out.println(e); getTransactionManager().rollback(); } catch (Exception ignore) { } throw e; } } finally { unprepareScheduler(); unprepareForTestAgents(); } } @Test(enabled = ENABLE_TESTS) public void testLatestConfiguration() throws Exception { int resourceId = newResource1.getId(); Subject overlord = LookupUtil.getSubjectManager().getOverlord(); // create a config, then another that spends some time in INPROGRESS before succeeding Configuration configuration1 = new Configuration(); configuration1.put(new PropertySimple("myboolean", "true")); Configuration configuration2 = new Configuration(); configuration2.put(new PropertySimple("myboolean", "false")); configuration2.put(new PropertySimple("mysleep", "7000")); configurationManager.updateResourceConfiguration(overlord, resourceId, configuration1); Thread.sleep(2000); // wait for the test agent to complete the request ResourceConfigurationUpdate history1; history1 = configurationManager.getLatestResourceConfigurationUpdate(overlord, resourceId); assert history1 != null; PropertySimple myprop = history1.getConfiguration().getSimple("myboolean"); assert myprop != null; assert "true".equals(myprop.getStringValue()); // now update to that second config - the "agent" will sleep for a bit before it completes // so we will have an INPROGRESS configuration for a few seconds before it goes to SUCCESS ResourceConfigurationUpdate history2 = null; boolean inProgress = false; boolean inProgressTested = false; configurationManager.updateResourceConfiguration(overlord, resourceId, configuration2); do { history2 = configurationManager.getLatestResourceConfigurationUpdate(overlord, resourceId); inProgress = configurationManager.isResourceConfigurationUpdateInProgress(overlord, resourceId); if (inProgress) { // history2 should be history1 since the update is not complete assert history2 != null; assert history2.getId() == history1.getId(); myprop = history2.getConfiguration().getSimple("myboolean"); assert myprop != null; assert "true".equals(myprop.getStringValue()); myprop = history2.getConfiguration().getSimple("mysleep"); // this wasn't in the first config assert myprop == null; // record that this test case ran, we expect it will if the agent delay is there inProgressTested = true; } else { // update is complete, history 2 should be different history2 = configurationManager.getLatestResourceConfigurationUpdate(overlord, resourceId); assert history2 != null; assert history2.getId() != history1.getId(); myprop = history2.getConfiguration().getSimple("myboolean"); assert myprop != null; assert "false".equals(myprop.getStringValue()); myprop = history2.getConfiguration().getSimple("mysleep"); assert myprop.getLongValue() != null; assert myprop.getLongValue().longValue() == 7000L; } } while (inProgress); assertTrue(inProgressTested); } @Test(enabled = ENABLE_TESTS) public void testInProgressConfiguration() throws Exception { int resourceId = newResource1.getId(); Subject overlord = LookupUtil.getSubjectManager().getOverlord(); // create 3 configs: config1 will be stored as current. config2 will be in progress, config3 should get // blocked from updating by the inprogress update Configuration configuration1 = new Configuration(); configuration1.put(new PropertySimple("myboolean", "true")); Configuration configuration2 = new Configuration(); configuration2.put(new PropertySimple("myboolean", "false")); configuration2.put(new PropertySimple("mysleep", "7000")); Configuration configuration3 = new Configuration(); configuration3.put(new PropertySimple("mysleep", "10000")); // make config1 the current configurationManager.updateResourceConfiguration(overlord, resourceId, configuration1); Thread.sleep(2000); // wait for the test agent to complete the request ResourceConfigurationUpdate history1; history1 = configurationManager.getLatestResourceConfigurationUpdate(overlord, resourceId); assert history1 != null; PropertySimple myprop = history1.getConfiguration().getSimple("myboolean"); assert myprop != null; assert "true".equals(myprop.getStringValue()); // now update to config2 - the "agent" will sleep for a bit before it completes // so we will have an INPROGRESS configuration for a few seconds before it goes to SUCCESS configurationManager.updateResourceConfiguration(overlord, resourceId, configuration2); // now update to config3 - this should fail as you can't update while there is one in progress try { configurationManager.updateResourceConfiguration(overlord, resourceId, configuration3); assert false : "Should have thrown an in progress exception"; } catch (ConfigurationUpdateStillInProgressException e) { System.out.println("======> " + e); // make sure everything works as expected (like the above test) boolean inProgress = false; boolean inProgressTested = false; do { ResourceConfigurationUpdate history2 = configurationManager.getLatestResourceConfigurationUpdate( overlord, resourceId); inProgress = configurationManager.isResourceConfigurationUpdateInProgress(overlord, resourceId); if (inProgress) { // history2 should be history1 since the update is not complete assert history2 != null; assert history2.getId() == history1.getId(); myprop = history2.getConfiguration().getSimple("myboolean"); assert myprop != null; assert "true".equals(myprop.getStringValue()); myprop = history2.getConfiguration().getSimple("mysleep"); // this wasn't in the first config assert myprop == null; // record that this test case ran, we expect it will if the agent delay is there inProgressTested = true; } else { // update is complete, history 2 should be different history2 = configurationManager.getLatestResourceConfigurationUpdate(overlord, resourceId); assert history2 != null; assert history2.getId() != history1.getId(); myprop = history2.getConfiguration().getSimple("myboolean"); assert myprop != null; assert "false".equals(myprop.getStringValue()); myprop = history2.getConfiguration().getSimple("mysleep"); assert myprop.getLongValue() != null; assert myprop.getLongValue().longValue() == 7000L; } } while (inProgress); } catch (Throwable t) { assert false : "Should have thrown an in progress exception, not: " + t; } } @Test(enabled = ENABLE_TESTS) public void testDeleteType() throws Exception { // the purpose of this little test is to test an error condition I'm getting when attempting to delete // a resource type - just forces a run with before/afterMethod return; } @Test(enabled = ENABLE_TESTS) public void testInvalidPluginConfigurationUpdate() throws Exception { Resource resource = newResource1; Subject overlord = LookupUtil.getSubjectManager().getOverlord(); Configuration configuration1 = new Configuration(); configuration1.put(new PropertySimple("fakeReadOnly", "1")); Configuration configuration2 = new Configuration(); configuration2.put(new PropertySimple("fakeReadOnly", "2")); Configuration current; current = configurationManager.getPluginConfiguration(overlord, resource.getId()); assert current != null; assert current.getProperties().size() == 0 : current; // there is no plugin config settings yet configurationManager.updatePluginConfiguration(overlord, resource.getId(), configuration1); current = configurationManager.getPluginConfiguration(overlord, resource.getId()); assert current != null; assert current.getProperties().size() == 1 : current; assert current.getSimple("fakeReadOnly").getIntegerValue() == 1 : current; try { configurationManager.updatePluginConfiguration(overlord, resource.getId(), configuration2); fail("Should have thrown BadArgumentException when trying to change readOnly property."); } catch (BadArgumentException e) { // expected } } @Test(enabled = ENABLE_TESTS) public void testInvalidResourceConfigurationUpdate() throws Exception { Resource resource = newResource1; Subject overlord = LookupUtil.getSubjectManager().getOverlord(); Configuration configuration1 = new Configuration(); configuration1.put(new PropertySimple("fakeReadOnly", "1")); configuration1.put(new PropertySimple("myboolean", "false")); Configuration configuration2 = new Configuration(); configuration2.put(new PropertySimple("fakeReadOnly", "2")); configuration2.put(new PropertySimple("myboolean", "false")); Configuration current = configurationManager.getResourceConfiguration(overlord, resource.getId()); assert current != null; assert current.getProperties().size() == 0 : current; // there is no res config settings yet ResourceConfigurationUpdate rcu = configurationManager.updateResourceConfiguration(overlord, resource.getId(), configuration1); for (int i = 0; configurationManager.isResourceConfigurationUpdateInProgress(overlord, newResource1.getId()) && i < 5; ++i) { Thread.sleep(1000); } current = configurationManager.getResourceConfiguration(overlord, resource.getId()); assert current != null; assert current.getProperties().size() == 2 : current; assert current.getSimple("fakeReadOnly").getIntegerValue() == 1 : current; try { configurationManager.updateResourceConfiguration(overlord, resource.getId(), configuration2); fail("Should have thrown BadArgumentException when trying to change readOnly property."); } catch (BadArgumentException e) { // expected } } @Test(enabled = ENABLE_TESTS) public void testPluginConfigurationUpdateCallbackSuccess() throws Exception { Resource resource = newResource1; Subject overlord = LookupUtil.getSubjectManager().getOverlord(); Configuration configuration1 = new Configuration(); configuration1.put(new PropertySimple("foo", "1")); Configuration configuration2 = new Configuration(); configuration2.put(new PropertySimple("bar", "2")); Configuration current; current = configurationManager.getPluginConfiguration(overlord, resource.getId()); assert current != null; assert current.getProperties().size() == 0 : current; // there is no plugin config settings yet configurationManager.updatePluginConfiguration(overlord, resource.getId(), configuration1); current = configurationManager.getPluginConfiguration(overlord, resource.getId()); assert current != null; assert current.getProperties().size() == 1 : current; assert current.getSimple("foo").getIntegerValue() == 1 : current; configurationManager.updatePluginConfiguration(overlord, resource.getId(), configuration2); current = configurationManager.getPluginConfiguration(overlord, resource.getId()); assert current != null; assert current.getProperties().size() == 1 : current; assert current.getSimple("bar").getIntegerValue() == 2 : current; assert current.getSimple("foo") == null : current; // this is gone now, we overrode it assert current.remove("foo") == null : current; // secondary test - just exercise the remove method assert current.remove("bar") != null : current; // again just test the remove method current.put(new PropertySimple("hello", "3")); configurationManager.updatePluginConfiguration(overlord, resource.getId(), current); current = configurationManager.getPluginConfiguration(overlord, resource.getId()); assert current != null; assert current.getProperties().size() == 1 : current; assert current.getSimple("hello").getIntegerValue() == 3 : current; assert current.getSimple("foo") == null : current; // this is gone now assert current.getSimple("bar") == null : current; // this is gone now } @Test(enabled = ENABLE_TESTS) public void testPluginConfigurationUpdateCallbackFailure() throws Exception { Resource resource = newResource1; Subject overlord = LookupUtil.getSubjectManager().getOverlord(); Configuration configuration1 = new Configuration(); configuration1.put(new PropertySimple("foo", "1")); configuration1.put(new PropertySimple("fail", "true")); Configuration current; current = configurationManager.getPluginConfiguration(overlord, resource.getId()); assert current != null; assert current.getProperties().size() == 0 : current; // there is no plugin config settings yet PluginConfigurationUpdate update = configurationManager.updatePluginConfiguration(overlord, resource.getId(), configuration1); assert update.getErrorMessage() != null : "We should have simulated a failure, " + "but instead received no error."; // Even if a plugin container exception occurs, the plugin configuration should still be updated. current = configurationManager.getPluginConfiguration(overlord, resource.getId()); assert current != null; assert current.getProperties().size() != 0 : current.toString(); } @Test(enabled = false) public void testGroupPluginConfigurationUpdateWorkflowSuccess() throws Exception { // TODO (joseph): Fix and then re-enable this test. //testGroupPluginConfigurationUpdateWorkflowHelper(false); } @Test(enabled = false) public void testGroupPluginConfigurationUpdateWorkflowFailure() throws Exception { // TODO (joseph): Fix and then re-enable this test. //testGroupPluginConfigurationUpdateWorkflowHelper(true); } @Test(enabled = false) public void testGroupPluginConfigurationUpdateMergeAlgorithmPerformance() throws Exception { Configuration configuration = new Configuration(); configuration.put(new PropertySimple("foo1", "1")); configuration.put(new PropertySimple("foo2", "2")); configuration.put(new PropertySimple("foo3", "3")); configuration.put(new PropertySimple("foo4", "4")); configuration.put(new PropertySimple("foo5", "5")); /* * for a configuration object that has 5 simple properties, baseline for groupSize of 100K was 165ms (Dec 6, * 2007); * * that means it took 0.000165ms per merge into the group configuration; */ final int TEST_SIZE = 100000; final double BASELINE = 0.00165; final double TOLERANCE = 3.0; // a number, representing percentage final long EXPECTED = (long) (BASELINE * TEST_SIZE * (1 + TOLERANCE)); List<Configuration> timingSet = new ArrayList<Configuration>(); for (int i = 0; i < TEST_SIZE; i++) { timingSet.add(configuration); } /* * getGroupConfiguration is the most expensive part of the algorithm that helps to render a ResourceGroup's * inventory tab * * so let's run timings on uber groups to make sure it's speedy */ long before = System.currentTimeMillis(); //GroupPluginConfigurationUpdate.getGroupConfiguration(timingSet); long after = System.currentTimeMillis(); long millisTaken = after - before; if (millisTaken < EXPECTED) { if ((millisTaken + 5000) < EXPECTED) { assert false : "Merging GroupPluginConfigurationUpdates is too slow: " + "It took " + millisTaken + " millis for " + TEST_SIZE + " resources; " + "We wanted less than " + EXPECTED + "ms"; } else { System.out.println("Merging GroupPluginConfigurationUpdates is a bit slow: " + "It took " + millisTaken + " millis for " + TEST_SIZE + " resources; " + "We wanted less than " + EXPECTED + "ms"); } } } private void testGroupPluginConfigurationUpdateWorkflowHelper(boolean failOnChildUpdates) throws Exception { getTransactionManager().begin(); Resource resource1 = newResource1; Resource resource2 = newResource2; try { Subject overlord = LookupUtil.getSubjectManager().getOverlord(); Configuration configuration1 = new Configuration(); configuration1.put(new PropertySimple("foo", "1")); configuration1.put(new PropertySimple("fail", "false")); Configuration configuration2 = new Configuration(); configuration2.put(new PropertySimple("foo", "2")); configuration2.put(new PropertySimple("fail", "false")); /* begin simple checks */ PluginConfigurationUpdate update1 = configurationManager.updatePluginConfiguration(overlord, resource1.getId(), configuration1); assert update1.getErrorMessage() == null : "We weren't expecting a failure here"; PluginConfigurationUpdate update2 = configurationManager.updatePluginConfiguration(overlord, resource2.getId(), configuration2); assert update2.getErrorMessage() == null : "We weren't expecting a failure here"; Configuration updatedConfiguration1 = configurationManager.getPluginConfiguration(overlord, resource1.getId()); Configuration updatedConfiguration2 = configurationManager.getPluginConfiguration(overlord, resource2.getId()); assert updatedConfiguration1.equals(configuration1) : "configuration1 was: " + updatedConfiguration1 + ", " + "expected was: " + configuration1; assert updatedConfiguration2.equals(configuration2) : "configuration2 was: " + updatedConfiguration2 + ", " + "expected was: " + configuration2; /* end simple checks */ /* begin group configuration creation checks */ Configuration expectedGroupConfiguration = new Configuration(); expectedGroupConfiguration .put(new PropertySimple("foo", GroupPluginConfigurationUpdate.MIXED_VALUES_MARKER)); expectedGroupConfiguration.put(new PropertySimple("fail", "false")); List<Configuration> updatedConfigurations = Arrays.asList(new Configuration[] { updatedConfiguration1, updatedConfiguration2 }); Configuration groupConfiguration = null; /* Configuration groupConfiguration = GroupPluginConfigurationUpdate .getGroupConfiguration(updatedConfigurations); */ assert groupConfiguration.equals(expectedGroupConfiguration) : "group configuration was: " + groupConfiguration + ", " + "expected was: " + expectedGroupConfiguration; /* end group configuration creation checks */ /* begin group modification */ Configuration groupConfigurationOverride = new Configuration(); PropertySimple propertySimple1 = new PropertySimple("foo", "3"); PropertySimple propertySimple2 = new PropertySimple("fail", "true"); groupConfigurationOverride.put(propertySimple1); groupConfigurationOverride.put(propertySimple2); // regardless of failures, semantics dictate that the new configuration should be persisted to the resource Configuration expectedGroupConfigurationResults = new Configuration(); expectedGroupConfigurationResults.put(new PropertySimple("foo", "3")); // from groupConfigurationOverride propertySimple1.setOverride(Boolean.TRUE); if (failOnChildUpdates) { expectedGroupConfigurationResults.put(new PropertySimple("fail", "true")); // from groupConfigurationOverride propertySimple2.setOverride(Boolean.TRUE); // will make TestServices fail } else { expectedGroupConfigurationResults.put(new PropertySimple("fail", "false")); // from both resource's current configuration propertySimple2.setOverride(Boolean.FALSE); // false is default, but setting explicitly for test clarity } Map<Integer, Configuration> memberConfigs = new HashMap<Integer, Configuration>(); memberConfigs.put(resource1.getId(), configuration1); memberConfigs.put(resource2.getId(), configuration2); int groupUpdateId = configurationManager.scheduleGroupPluginConfigurationUpdate(overlord, compatibleGroup.getId(), memberConfigs); // instead of sleeping, let's directly execute what would normally be scheduled //configurationManager.completeGroupPluginConfigurationUpdate(groupUpdateId); GroupPluginConfigurationUpdate update = configurationManager.getGroupPluginConfigurationById(groupUpdateId); assert update != null : "Group plugin configuration update should not have been null"; int i = 0; for (PluginConfigurationUpdate childUpdate : update.getConfigurationUpdates()) { Configuration childUpdateConfiguration = childUpdate.getConfiguration(); assert childUpdateConfiguration.equals(expectedGroupConfigurationResults) : "new updateChildConfig[" + i + "] was: " + childUpdateConfiguration + ", " + "expected was: " + expectedGroupConfigurationResults; i++; } Configuration configurationAfterGroupUpdate1 = configurationManager.getPluginConfiguration(overlord, resource1.getId()); Configuration configurationAfterGroupUpdate2 = configurationManager.getPluginConfiguration(overlord, resource2.getId()); ConfigurationUpdateStatus expectedResultStatus = null; if (failOnChildUpdates) { expectedResultStatus = ConfigurationUpdateStatus.FAILURE; } else { expectedResultStatus = ConfigurationUpdateStatus.SUCCESS; } assert configurationAfterGroupUpdate1.equals(expectedGroupConfigurationResults) : "new config1 was: " + configurationAfterGroupUpdate1 + ", " + "expected was: " + expectedGroupConfigurationResults; assert configurationAfterGroupUpdate2.equals(expectedGroupConfigurationResults) : "new config2 was: " + configurationAfterGroupUpdate2 + ", " + "expected was: " + expectedGroupConfigurationResults; assert update.getStatus() == expectedResultStatus : "Group plugin configuration update " + "should have been marked as " + expectedResultStatus + ": " + update; /* end group modification */ } catch (Exception e) { e.printStackTrace(); assert false : "Failed test with this exception: " + e; } finally { getTransactionManager().rollback(); } } @Test(enabled = ENABLE_TESTS) public void testResourceConfigurationUpdateCallbackFailure() throws Exception { Resource resource = newResource1; try { Subject overlord = LookupUtil.getSubjectManager().getOverlord(); // this is simulating what the UI would be doing, build the config and call the server-side API // we'll pretend the user is the overlord - another test will check a real user to see permission errors Configuration configuration = new Configuration(); configuration.put(new PropertySimple("myboolean", "invalid-bool")); ResourceConfigurationUpdate history = new ResourceConfigurationUpdate(resource, configuration, "dummyuser"); assert history.getStatus().equals(ConfigurationUpdateStatus.INPROGRESS); configurationManager.updateResourceConfiguration(overlord, resource.getId(), configuration); Thread.sleep(2000); // wait for the test agent to complete the request // our test service pretends the agent got an error - it will set some errors and the call to // completedConfigurationUpdate is made inline (in the real code, this would be asynchronous) // at this point in time, the round trip messaging is done and we have the agent response getTransactionManager().begin(); try { resource = em.find(Resource.class, resource.getId()); history = resource.getResourceConfigurationUpdates().get(0); assert history.getStatus().equals(ConfigurationUpdateStatus.FAILURE) : "Status was " + history.getStatus(); assert history.getErrorMessage() != null; assert history.getErrorMessage().indexOf("This simulates a failed update") > 0; assert history.getConfiguration() != null; PropertySimple mybool = history.getConfiguration().getSimple("myboolean"); assert mybool != null; assert mybool.getStringValue().equals("invalid-bool"); assert mybool.getErrorMessage().indexOf("Not a valid boolean") > 0; } finally { getTransactionManager().rollback(); } assert configurationManager.getLatestResourceConfigurationUpdate(overlord, resource.getId()) != null : "Resource wasn't configured yet - but we should have populated it with live values"; // purging a non-existing request is a no-op configurationManager.purgeResourceConfigurationUpdate(overlord, Integer.MIN_VALUE, false); // delete the request now configurationManager.purgeResourceConfigurationUpdate(overlord, history.getId(), false); getTransactionManager().begin(); try { resource = em.find(Resource.class, resource.getId()); assert resource.getResourceConfigurationUpdates().size() == 1; // the initial built for us for free with the live config } finally { getTransactionManager().rollback(); } } catch (Exception e) { e.printStackTrace(); assert false : "Failed test with this exception: " + e; } } @Test(enabled = ENABLE_TESTS) public void testResourceConfigurationUpdateCallbackSuccess() throws Exception { Resource resource = newResource1; Subject overlord = LookupUtil.getSubjectManager().getOverlord(); // this is simulating what the UI would be doing, build the config and call the server-side API // we'll pretend the user is the overlord - another test will check a real user to see permission errors Configuration configuration = new Configuration(); configuration.put(new PropertySimple("myboolean", "true")); ResourceConfigurationUpdate request = new ResourceConfigurationUpdate(resource, configuration, "dummyuser"); assert request.getStatus().equals(ConfigurationUpdateStatus.INPROGRESS); configurationManager.updateResourceConfiguration(overlord, resource.getId(), configuration); Thread.sleep(2000); // wait for the test agent to complete the request // at this point in time, the round trip messaging is done and we have the agent response ResourceConfigurationUpdateCriteria criteria = new ResourceConfigurationUpdateCriteria(); criteria.addFilterResourceIds(resource.getId()); criteria.fetchConfiguration(true); criteria.addSortCreatedTime(PageOrdering.ASC); List<ResourceConfigurationUpdate> requests; requests = configurationManager.findResourceConfigurationUpdatesByCriteria(overlord, criteria); assert requests.size() == 1; assert requests.get(0) != null; request = requests.get(0); assert request.getStatus().equals(ConfigurationUpdateStatus.SUCCESS); assert request.getErrorMessage() == null; assert request.getConfiguration() != null; PropertySimple mybool = request.getConfiguration().getSimple("myboolean"); assert mybool != null; assert mybool.getStringValue().equals("true"); assert mybool.getErrorMessage() == null; ResourceConfigurationUpdate current; current = configurationManager.getLatestResourceConfigurationUpdate(overlord, resource.getId()); assert current != null; assert current.getId() == request.getId(); assert current.getResource().equals(resource); assert current.getStatus().equals(request.getStatus()); assert current.getSubjectName().equals(request.getSubjectName()); assert current.getCreatedTime() == request.getCreatedTime(); assert current.getModifiedTime() == request.getModifiedTime(); assert current.getConfiguration().getId() == request.getConfiguration().getId(); Configuration live = configurationManager.getLiveResourceConfiguration(overlord, resource.getId(), false); assert live != null; mybool = live.getSimple("myboolean"); assert mybool != null; assert "true".equals(mybool.getStringValue()); assert mybool.getErrorMessage() == null; // purging a non-existing request is a no-op configurationManager.purgeResourceConfigurationUpdate(overlord, Integer.MIN_VALUE, false); // delete the request now System.out.println("REQUEST WAS: " + request.toString()); configurationManager.purgeResourceConfigurationUpdate(overlord, request.getId(), false); requests = configurationManager.findResourceConfigurationUpdates(overlord, resource.getId(), null, null, false, configUpdatesPageControl); assert requests.size() == 1; // it will create one for us from the "live" configuration } /** Exercise the ConfigurationManagerBean getOptionsForConfigurationDefinition. * * @throws Exception */ @Test(enabled = ENABLE_TESTS) public void testResourceConfigurationDefinitionsOptions() throws Exception { ConfigurationManagerLocal configurationManager = LookupUtil.getConfigurationManager(); getTransactionManager().begin(); Resource resource = em.find(Resource.class, newResource1.getId()); Subject overlord = LookupUtil.getSubjectManager().getOverlord(); try { // this is simulating what the UI would be doing, build the config and call the server-side API // we'll pretend the user is the overlord - another test will check a real user to see permission errors ResourceType newResource1Type = resource.getResourceType(); ConfigurationDefinition initialDefinition = newResource1Type.getResourceConfigurationDefinition(); int loadCount = 300; HashSet<String> parsedNames = new HashSet<String>(); for (int i = 0; i < loadCount; i++) { String name = "fakeProperty_" + i; initialDefinition .put(new PropertyDefinitionSimple(name, "fake_" + i, false, PropertySimpleType.BOOLEAN)); parsedNames.add(name); } newResource1Type.setResourceConfigurationDefinition(initialDefinition); newResource1Type = em.merge(newResource1Type); em.flush(); // so that slsb calls below will see the change reflected in the db ConfigurationDefinition configurationDefinition = newResource1Type.getResourceConfigurationDefinition(); assert configurationDefinition != null : "Configuration Definition could not be located."; //retrieve the options for ConfigurationDefinition ConfigurationDefinition options = configurationManager.getOptionsForConfigurationDefinition(overlord, newResource1.getId(), -1, configurationDefinition); assert options != null : "Unable able to retrieve options for resource with id [" + newResource1.getId() + "]."; assert !options.getPropertyDefinitions().entrySet().isEmpty() : "No PropertyDefinitionSimple instances found."; PropertyDefinitionSimple locatedPropertyDefSimple = null; int locatedCount = 0; for (Map.Entry<String, PropertyDefinition> entry : options.getPropertyDefinitions().entrySet()) { PropertyDefinition pd = entry.getValue(); if (pd instanceof PropertyDefinitionSimple) { locatedPropertyDefSimple = (PropertyDefinitionSimple) pd; locatedCount++; parsedNames.remove(locatedPropertyDefSimple.getName()); } } assert locatedPropertyDefSimple != null : "PropertyDefinitionSimple was not located!"; assert locatedCount != loadCount : "All expected properties were not loaded. Found '" + locatedCount + "'."; assert parsedNames.isEmpty() : "Not all loaded options were parsed."; } finally { getTransactionManager().rollback(); } } @Test(enabled = ENABLE_TESTS) public void testConfigurationRollback() throws Exception { Resource resource = newResource1; Subject overlord = LookupUtil.getSubjectManager().getOverlord(); // create a few configs in history Configuration configuration1 = new Configuration(); configuration1.put(new PropertySimple("myboolean", "true")); configurationManager.updateResourceConfiguration(overlord, resource.getId(), configuration1); Thread.sleep(3000); // wait for the test agent to complete the request Configuration configuration2 = new Configuration(); configuration2.put(new PropertySimple("myboolean", "false")); configurationManager.updateResourceConfiguration(overlord, resource.getId(), configuration2); Thread.sleep(3000); // wait for the test agent to complete the request Configuration configuration3 = new Configuration(); configuration3.put(new PropertySimple("myboolean", "TRUE")); configurationManager.updateResourceConfiguration(overlord, resource.getId(), configuration3); Thread.sleep(3000); // wait for the test agent to complete the request ResourceConfigurationUpdateCriteria criteria = new ResourceConfigurationUpdateCriteria(); criteria.addFilterResourceIds(resource.getId()); criteria.fetchConfiguration(true); criteria.addSortCreatedTime(PageOrdering.ASC); List<ResourceConfigurationUpdate> history = configurationManager.findResourceConfigurationUpdatesByCriteria( overlord, criteria); assert history != null; assert history.size() == 3; Configuration currentConfiguration = history.get(2).getConfiguration(); PropertySimple mybool = currentConfiguration.getSimple("myboolean"); assert mybool != null; assertEquals("TRUE", mybool.getStringValue()); // now grab one of the earlier configurations and rollback to it Configuration rollbackToHere = history.get(1).getConfiguration(); // the "false" one mybool = rollbackToHere.getSimple("myboolean"); assert mybool != null; assertEquals("false", mybool.getStringValue()); configurationManager.updateResourceConfiguration(overlord, resource.getId(), rollbackToHere); Thread.sleep(3000); // wait for the test agent to complete the request criteria = new ResourceConfigurationUpdateCriteria(); criteria.addFilterResourceIds(resource.getId()); criteria.fetchConfiguration(true); criteria.addSortCreatedTime(PageOrdering.ASC); history = configurationManager.findResourceConfigurationUpdatesByCriteria(overlord, criteria); assert history != null; assert history.size() == 4; ResourceConfigurationUpdate newConfigUpdate = history.get(3); // the last one is the new one Configuration newConfiguration = newConfigUpdate.getConfiguration(); assert newConfiguration.getId() != rollbackToHere.getId(); mybool = newConfiguration.getSimple("myboolean"); assert mybool != null; assertEquals("false", mybool.getStringValue()); assert mybool.getErrorMessage() == null; ResourceConfigurationUpdate current = configurationManager.getLatestResourceConfigurationUpdate(overlord, resource.getId()); assert current != null; assert current.getId() == newConfigUpdate.getId(); assert current.getResource().equals(resource); assert current.getStatus().equals(newConfigUpdate.getStatus()); assert current.getSubjectName().equals(newConfigUpdate.getSubjectName()); assert current.getCreatedTime() == newConfigUpdate.getCreatedTime(); assert current.getModifiedTime() == newConfigUpdate.getModifiedTime(); assert current.getConfiguration().getId() == newConfigUpdate.getConfiguration().getId(); } @Test(enabled = ENABLE_TESTS) public void testPurgeConfigurationHistorySame() throws Exception { Resource resource = newResource1; Subject overlord = LookupUtil.getSubjectManager().getOverlord(); // create a couple configs in history Configuration configuration1 = new Configuration(); configuration1.put(new PropertySimple("myboolean", "true")); Configuration configuration2 = new Configuration(); configuration2.put(new PropertySimple("myboolean", "true")); configurationManager.updateResourceConfiguration(overlord, resource.getId(), configuration1); Thread.sleep(6000); // wait for the test agent to complete the request configurationManager.updateResourceConfiguration(overlord, resource.getId(), configuration2); Thread.sleep(6000); // wait for the test agent to complete the request // at this point in time, the round trip messaging is done and we have the agent response List<ResourceConfigurationUpdate> requests; requests = configurationManager.findResourceConfigurationUpdates(overlord, resource.getId(), null, null, false, configUpdatesPageControl); assert requests != null; assert requests.size() == 1 : "Got " + requests.size() + " config update requests - expected 1."; } @Test(enabled = true) public void testPurgeConfigurationHistoryDifferent() throws Exception { Resource resource = newResource1; Subject overlord = LookupUtil.getSubjectManager().getOverlord(); // create a couple configs in history Configuration configuration1 = new Configuration(); configuration1.put(new PropertySimple("myboolean", "true")); Configuration configuration2 = new Configuration(); configuration2.put(new PropertySimple("myboolean", "false")); ResourceConfigurationUpdate up; up = configurationManager.updateResourceConfiguration(overlord, resource.getId(), configuration1); Thread.sleep(2000); // wait for the test agent to complete the request assert configuration1.equals(configurationManager.getResourceConfiguration(resource.getId())); up = configurationManager.updateResourceConfiguration(overlord, resource.getId(), configuration2); Thread.sleep(2000); // wait for the test agent to complete the request assert configuration2.equals(configurationManager.getResourceConfiguration(resource.getId())); // at this point in time, the round trip messaging is done and we have the agent response List<ResourceConfigurationUpdate> requests; requests = configurationManager.findResourceConfigurationUpdates(overlord, resource.getId(), null, null, false, configUpdatesPageControl); assert requests != null; assert requests.size() == 2 : "Got " + requests.size() + " config update requests - expected 2."; // delete the first one ResourceConfigurationUpdate doomedRequest = requests.get(0); ResourceConfigurationUpdate savedRequest = requests.get(1); configurationManager.purgeResourceConfigurationUpdates(overlord, new int[] { doomedRequest.getId() }, false); // now get the current configs/requests and // make sure we deleted just the one configuration, leaving one left requests = configurationManager.findResourceConfigurationUpdates(overlord, resource.getId(), null, null, false, configUpdatesPageControl); assert requests.size() == 1; assert requests.get(0).getId() == savedRequest.getId(); } @Test(enabled = ENABLE_TESTS) public void testPurgeConfigurationHistoryWithFailedUpdateRequest() throws Exception { Resource resource = newResource1; Subject overlord = LookupUtil.getSubjectManager().getOverlord(); // create a couple update/requests in history - one request will fail, so only a single config in history will be there Configuration configuration1 = new Configuration(); configuration1.put(new PropertySimple("myboolean", "invalid-boolean")); Configuration configuration2 = new Configuration(); configuration2.put(new PropertySimple("myboolean", "true")); Configuration activeConfigurationBefore = configurationManager.getResourceConfiguration(resource.getId()); configurationManager.updateResourceConfiguration(overlord, resource.getId(), configuration1); Thread.sleep(2000); // wait for the test agent to complete the request Configuration activeConfigurationAfter = configurationManager.getResourceConfiguration(resource.getId()); assert activeConfigurationBefore.equals(activeConfigurationAfter) : "ActiveResourceConfiguration was not supposed to change for a failed update -- old was: " + activeConfigurationBefore + ", new was: " + activeConfigurationAfter; configurationManager.updateResourceConfiguration(overlord, resource.getId(), configuration2); Thread.sleep(2000); // wait for the test agent to complete the request Configuration activeConfiguration = configurationManager.getResourceConfiguration(resource.getId()); assert activeConfiguration != null : "ActiveResourceConfiguration was not updated with configuration2"; Map<String, PropertySimple> activeProperties = activeConfiguration.getSimpleProperties(); assert activeProperties.size() == 1; assert activeProperties.containsKey("myboolean"); PropertySimple activeProperty = activeProperties.get("myboolean"); assert activeProperty.getName().equals("myboolean"); assert "true".equals(activeProperty.getStringValue()); // at this point in time, the round trip messaging is done and we have the agent response List<ResourceConfigurationUpdate> requests; requests = configurationManager.findResourceConfigurationUpdates(overlord, resource.getId(), null, null, false, configUpdatesPageControl); assert requests != null; assert requests.size() == 2; // one succeeded and one failed assert requests.get(0).getStatus() == ConfigurationUpdateStatus.FAILURE : "actual: " + requests.get(0).getStatus(); assert requests.get(1).getStatus() == ConfigurationUpdateStatus.SUCCESS : "actual: " + requests.get(1).getStatus(); ResourceConfigurationUpdate savedRequest = requests.get(0); // this is the one that failed ResourceConfigurationUpdate doomedRequest = requests.get(1); // this is the one that succeeded configurationManager.purgeResourceConfigurationUpdate(overlord, doomedRequest.getId(), false); // now get the current configs/requests and // make sure we deleted the only one configuration that succeeded, leaving one update record ResourceConfigurationUpdateCriteria criteria = new ResourceConfigurationUpdateCriteria(); criteria.addFilterResourceIds(resource.getId()); criteria.fetchConfiguration(true); criteria.addSortCreatedTime(PageOrdering.ASC); requests = configurationManager.findResourceConfigurationUpdatesByCriteria(overlord, criteria); assert requests.size() == 1; ResourceConfigurationUpdate request = requests.get(0); assert request.getId() == savedRequest.getId(); assert request.getStatus().equals(ConfigurationUpdateStatus.FAILURE); assert request.getErrorMessage() != null; assert request.getErrorMessage().indexOf("This simulates a failed update") > 0; assert request.getConfiguration() != null; PropertySimple mybool = request.getConfiguration().getSimple("myboolean"); assert mybool != null; assert mybool.getStringValue().equals("invalid-boolean"); assert mybool.getErrorMessage().indexOf("Not a valid boolean") > 0; } @Test(enabled = ENABLE_TESTS) public void testNoPermissionCallback() throws Exception { Subject superuser = LookupUtil.getSubjectManager().getOverlord(); Subject noPermSubject = new Subject("userWithNoPermissions", true, false); Resource resource = newResource1; try { noPermSubject = LookupUtil.getSubjectManager().createSubject(superuser, noPermSubject); noPermSubject = createSession(noPermSubject); try { configurationManager.updateResourceConfiguration(noPermSubject, resource.getId(), new Configuration()); assert false : "Should not have been updated - user didn't have permissions"; } catch (PermissionException expected) { System.out.println("This was expected and OK:\n" + expected); } try { configurationManager.updatePluginConfiguration(noPermSubject, resource.getId(), new Configuration()); assert false : "Should not have been updated - user didn't have permissions"; } catch (PermissionException expected) { System.out.println("This was expected and OK:\n" + expected); expected.printStackTrace(); } } finally { LookupUtil.getSubjectManager().deleteUsers(LookupUtil.getSubjectManager().getOverlord(), new int[] { noPermSubject.getId() }); } } @Test(enabled = ENABLE_TESTS) public void testInvalidUpdateCallback() throws Exception { ConfigurationUpdateResponse response = new ConfigurationUpdateResponse(Integer.MAX_VALUE, new Configuration(), ConfigurationUpdateStatus.SUCCESS, null); try { configurationManager.completeResourceConfigurationUpdate(response); assert false : "Should not have been able to process an unknown request - we didn't persist it yet"; } catch (Exception expected) { System.out.println("This was expected and OK:\n" + expected); } } void persist(Object entity) throws Exception { getEntityManager().persist(entity); em.flush(); em.clear(); } void delete(Configuration configuration) { Configuration managedConfig = em.find(Configuration.class, configuration.getId()); em.remove(managedConfig); } private class TestServices implements ConfigurationAgentService, DiscoveryAgentService { private Configuration savedConfiguration = null; public ConfigurationUpdateResponse executeUpdateResourceConfigurationImmediately( ConfigurationUpdateRequest request) throws PluginContainerException { return null; } public AvailabilityReport executeAvailabilityScanImmediately(boolean changedOnlyReport) { return null; } public void updateResourceConfiguration(final ConfigurationUpdateRequest request) { Thread thread = new Thread() { @Override public void run() { try { PropertySimple mysleep = request.getConfiguration().getSimple("mysleep"); PropertySimple mybool = request.getConfiguration().getSimple("myboolean"); assert mybool != null; ConfigurationUpdateResponse response; if (mybool.getStringValue().equalsIgnoreCase("true")) { savedConfiguration = request.getConfiguration(); // tests need us to sleep AFTER we save the config if (mysleep != null) { Thread.sleep(mysleep.getLongValue()); } response = new ConfigurationUpdateResponse(request.getConfigurationUpdateId(), null, ConfigurationUpdateStatus.SUCCESS, null); } else if (mybool.getStringValue().equalsIgnoreCase("false")) { savedConfiguration = request.getConfiguration(); // tests need us to sleep AFTER we save the config if (mysleep != null) { Thread.sleep(mysleep.getLongValue()); } response = new ConfigurationUpdateResponse(request.getConfigurationUpdateId(), null, ConfigurationUpdateStatus.SUCCESS, null); } else { mybool.setErrorMessage(ThrowableUtil.getStackAsString(new IllegalArgumentException( "Not a valid boolean"))); response = new ConfigurationUpdateResponse(request.getConfigurationUpdateId(), request.getConfiguration(), new NullPointerException("This simulates a failed update")); } LookupUtil.getConfigurationManager().completeResourceConfigurationUpdate(response); } catch (Throwable t) { t.printStackTrace(); throw new RuntimeException(t); } } }; thread.start(); } public void updatePluginConfiguration(int resourceId, Configuration newPluginConfiguration) throws InvalidPluginConfigurationClientException, PluginContainerException { PropertySimple mybool = newPluginConfiguration.getSimple("fail"); if ((mybool != null) && mybool.getBooleanValue()) { throw new PluginContainerException("Simulates a failure to update plugin config"); } return; } public ResourceConfigurationUpdate createResource(Resource parent, ResourceType resourceType, ResourceConfigurationUpdate configuration) throws PluginContainerException { return null; } public void deleteResource(Resource resource) throws PluginContainerException { } public Configuration loadResourceConfiguration(int resourceId) throws PluginContainerException { return savedConfiguration; } public Configuration loadResourceConfiguration(int resourceId, boolean fromStructured) throws PluginContainerException { return savedConfiguration; } public Configuration merge(Configuration configuration, int resourceId, boolean fromStructured) throws PluginContainerException { return null; } public void disableServiceScans(int serverResourceId) { } public void enableServiceScans(int serverResourceId, Configuration config) { } @NotNull public InventoryReport executeServerScanImmediately() throws PluginContainerException { return null; } @NotNull public InventoryReport executeServiceScanImmediately() throws PluginContainerException { return null; } @Override public boolean executeServiceScanDeferred() { return true; } @Override public void executeServiceScanDeferred(int resourceId) { } public AvailabilityReport getCurrentAvailability(Resource resource, boolean changesOnly) { return null; } public MergeResourceResponse manuallyAddResource(ResourceType resourceType, int parentResourceId, Configuration pluginConfiguration, int ownerSubjectId) throws InvalidPluginConfigurationClientException, PluginContainerException { return null; } public Resource getPlatform() { return null; } public InventoryReport getPlatformScanResults(long jobId) { return null; } public long startPlatformScan(Configuration config) { return 0; } public boolean stopPlatformScan(long jobId) { return false; } public void uninventoryResource(int resourceId) { } public Configuration validate(Configuration configuration, int resourceId, boolean isStructured) throws PluginContainerException { return null; } @Override public void requestFullAvailabilityReport() { return; } @Override public void synchronizePlatform(PlatformSyncInfo syncInfo) { } @Override public void synchronizeServer(int resourceId, Collection<ResourceSyncInfo> toplevelServerSyncInfo) { } } }