/* * RHQ Management Platform * Copyright (C) 2005-2014 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.core.pc.drift; import static java.util.Arrays.asList; import static org.apache.commons.io.FileUtils.deleteDirectory; import static org.rhq.core.domain.drift.DriftChangeSetCategory.COVERAGE; import static org.rhq.core.util.ZipUtil.unzipFile; import static org.rhq.test.AssertUtils.assertCollectionMatchesNoOrder; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import org.rhq.core.clientapi.server.drift.DriftServerService; import org.rhq.core.domain.drift.DriftComplianceStatus; import org.rhq.core.domain.drift.DriftDefinition; import org.rhq.core.domain.drift.DriftSnapshot; import org.rhq.core.domain.resource.Resource; import org.rhq.core.pc.PluginContainerConfiguration; import org.rhq.core.pc.ServerServices; import org.rhq.core.pc.inventory.InventoryManager; import org.rhq.core.pc.inventory.ResourceContainer; import org.rhq.core.pc.plugin.PluginLifecycleListenerManagerImpl; import org.rhq.core.pc.plugin.PluginManager; public class DriftManagerTest extends DriftTest { private File tmpDir; private TestDriftServerService driftServerService; private PluginContainerConfiguration pcConfig; private PluginManager pluginManager; private TestDriftManager driftMgr; @BeforeMethod public void initTest() throws Exception { tmpDir = mkdir(basedir(), "tmp"); pcConfig = new PluginContainerConfiguration(); pluginManager = new PluginManager(pcConfig, new PluginLifecycleListenerManagerImpl()); ServerServices serverServices = new ServerServices(); driftServerService = new TestDriftServerService(); serverServices.setDriftServerService(driftServerService); pcConfig.setServerServices(serverServices); pcConfig.setDataDirectory(basedir()); pcConfig.setTemporaryDirectory(tmpDir); driftMgr = new TestDriftManager(pcConfig); driftMgr.setChangeSetMgr(changeSetMgr); } @Test public void writeChangeSetZipFileToChangeSetDirectory() throws Exception { final DriftDefinition config = driftDefinition("write-changeset-file", resourceDir.getAbsolutePath()); final File changeSetDir = changeSetDir(config.getName()); createRandomFile(changeSetDir, "changeset.txt"); setDriftServiceCallback(new DriftServiceCallback() { @Override public void execute() { assertThatZipFileExists(changeSetDir, "changeset_", "Expected to find change set zip file " + "in " + changeSetDir.getPath() + ". The file name should follow the pattern " + "changeset_<integer_timestamp>.zip"); } }); DriftDetectionSchedule schedule = new DriftDetectionSchedule(resourceId(), config); DriftDetectionSummary detectionSummary = new DriftDetectionSummary(); detectionSummary.setSchedule(schedule); detectionSummary.setType(COVERAGE); driftMgr.setChangeSetMgr(changeSetMgr); driftMgr.getSchedulesQueue().addSchedule(schedule); driftMgr.sendChangeSetToServer(detectionSummary); } @Test public void sendChangeSetReportInZipFile() throws Exception { final DriftDefinition config = driftDefinition("send-changeset-in-zip", resourceDir.getAbsolutePath()); final File changeSetDir = changeSetDir(config.getName()); final File changeSetFile = createRandomFile(changeSetDir, "changeset.txt"); setDriftServiceCallback(new DriftServiceCallback() { @Override public void execute() { assertZipFileMatches(driftServerService.inputStream, changeSetFile); } }); DriftDetectionSchedule schedule = new DriftDetectionSchedule(resourceId(), config); DriftDetectionSummary detectionSummary = new DriftDetectionSummary(); detectionSummary.setSchedule(schedule); detectionSummary.setType(COVERAGE); driftMgr.setChangeSetMgr(changeSetMgr); driftMgr.getSchedulesQueue().addSchedule(new DriftDetectionSchedule(resourceId(), config)); driftMgr.sendChangeSetToServer(detectionSummary); } @Test public void cleanUpWhenServerAcksChangeSet() throws Exception { DriftDefinition config = driftDefinition("clean-up-when-server-acks-changeset", resourceDir.getAbsolutePath()); File changeSetDir = changeSetDir(config.getName()); File snapshotFile = createRandomFile(changeSetDir, "changeset.txt"); File previousSnapshotFile = createRandomFile(changeSetDir, "changeset.txt.previous"); createRandomFile(changeSetDir, "changeset_" + System.currentTimeMillis() + ".zip"); driftMgr.ackChangeSet(resourceId(), config.getName()); assertTrue(snapshotFile.exists(), "Snapshot file should exist after server acks change set"); assertFalse(previousSnapshotFile.exists(), "Previous version snapshot file should be deleted when server " + "acks change set"); assertEquals(findChangeSetZipFiles(changeSetDir).size(), 0, "All change set zip files should be deleted when " + "server acks change set"); } @Test public void cleanUpWhenServerAcksChangeSetContent() throws Exception { String configName = "cleanup-when-server-acks-content"; File changeSetDir = changeSetDir(configName); mkdir(changeSetDir, "content"); String token = Long.toString(System.currentTimeMillis()); File contentZipFile = createRandomFile(changeSetDir, "content_" + token + ".zip"); driftMgr.ackChangeSetContent(resourceId(), configName, token); assertFalse(contentZipFile.exists(), "Content zip file should be purged after server sends content ack"); } @Test public void unschedulingDetectionRemovesScheduleFromQueue() throws Exception { DriftDefinition config = driftDefinition("remove-from-queue", resourceDir.getAbsolutePath()); driftMgr.scheduleDriftDetection(resourceId(), config); driftMgr.scheduleDriftDetection(resourceId() + 5, driftDefinition("another-config", ".")); driftMgr.unscheduleDriftDetection(resourceId(), config); assertFalse(driftMgr.getSchedulesQueue().contains(resourceId(), config), new DriftDetectionSchedule( resourceId(), config) + " should have been removed from the schedule queue"); } @Test public void unschedulingDetectionRemovesDriftDefFromResourceContainer() throws Exception { DriftDefinition def = driftDefinition("remove-from-queue", resourceDir.getAbsolutePath()); DriftDefinition def2 = driftDefinition("do-not-remove", resourceDir.getAbsolutePath()); driftMgr.scheduleDriftDetection(resourceId(), def); driftMgr.scheduleDriftDetection(resourceId(), def2); driftMgr.unscheduleDriftDetection(resourceId(), def); ResourceContainer container = driftMgr.getInventoryManager().getResourceContainer(resourceId()); assertCollectionMatchesNoOrder(def + " should have been removed from the resource container ", asList(def2), container.getDriftDefinitions()); } @Test public void unschedulingDetectionDeletesChangeSetDirectoryWhenScheduleIsNotActive() throws Exception { DriftDefinition config = driftDefinition("delete-changeset-dir", resourceDir.getAbsolutePath()); File changeSetDir = changeSetDir(config.getName()); File contentDir = mkdir(changeSetDir, "content"); createRandomFile(contentDir, "my_content"); createRandomFile(changeSetDir, "changeset.txt"); driftMgr.scheduleDriftDetection(resourceId(), config); driftMgr.unscheduleDriftDetection(resourceId(), config); assertFalse(changeSetDir.exists(), "The change set directory should have been deleted."); } @Test public void unschedulingDetectionDeletesChangeSetDirectoryWhenScheduleIsDeactivated() throws Exception { DriftDefinition config = driftDefinition("delete-changeset-dir", resourceDir.getAbsolutePath()); File changeSetDir = changeSetDir(config.getName()); File contentDir = mkdir(changeSetDir, "content"); createRandomFile(contentDir, "my_content"); createRandomFile(changeSetDir, "changeset.txt"); driftMgr.scheduleDriftDetection(resourceId(), config); driftMgr.getSchedulesQueue().getNextSchedule(); driftMgr.unscheduleDriftDetection(resourceId(), config); assertTrue(changeSetDir.exists(), "The change set directory should not be deleted while the schedule is " + "still active."); driftMgr.getSchedulesQueue().deactivateSchedule(false); assertFalse(changeSetDir.exists(), "The change set directory should have been deleted after the schedule is " + "deactivated."); } private List<File> findChangeSetZipFiles(File dir) { File[] files = dir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.startsWith("changeset_") && name.endsWith(".zip"); } }); return asList(files); } /** * This method first verifies that each of the expected files is contained in the the * zip file. Then it verifies that the content for each file in the zip file matches * the expected files by comparing their SHA-256 hashes. * * @param zipStream * @param expectedFiles * @throws IOException */ private void assertZipFileMatches(InputStream zipStream, File... expectedFiles) { File zipDir = new File(tmpDir, "output"); try { deleteDirectory(zipDir); zipDir.mkdirs(); unzipFile(zipStream, zipDir); } catch (IOException e) { fail("An error occurred while trying to unzip " + zipDir.getPath(), e); } assertEquals(zipDir.listFiles().length, expectedFiles.length, "The zip file has the wrong number of files"); for (File expectedFile : expectedFiles) { File actualFile = findFile(zipDir, expectedFile); assertNotNull(actualFile, "Expected zip file to contain " + expectedFile.getName()); String expectedHash = sha256(expectedFile); String actualHash = sha256(actualFile); assertEquals(actualHash, expectedHash, "The zip file content is wrong. The SHA-256 hash does not match " + "for " + expectedFile.getName()); } } private File findFile(File dir, File file) { for (File f : dir.listFiles()) { if (f.getName().equals(file.getName())) { return f; } } return null; } /** * This method searches the specified directory for a zip file with the specified * prefix. The file name must start with the prefix and end with an extension of .zip. * This method assumes that there should be one and only one match. If there is not * exactly one match, it will fail the test. * * @param dir The directory to search (sub directories are not searched) * @param fileNamePrefix The zip file name prefix * @param msg An error message * @return The matching zip file */ private File assertThatZipFileExists(final File dir, final String fileNamePrefix, String msg) { File[] files = dir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.startsWith(fileNamePrefix) && name.endsWith(".zip"); } }); assertEquals(files.length, 1, msg); return files[0]; } /** * Sets a callback that will be invoked immediately after DriftManager calls * {@link DriftServerService#sendChangesetZip(int, long, java.io.InputStream)} or * {@link DriftServerService#sendFilesZip(int, long, java.io.InputStream)}. The callback * can perform any verification as necessary, and that will happen before the call to * to DriftServerService returns. * * @param callback */ private void setDriftServiceCallback(DriftServiceCallback callback) { driftServerService.callback = callback; } private static class TestDriftServerService implements DriftServerService { public int resourceId; public String driftDefName; public String token; public long fileSize; public InputStream inputStream; public DriftServiceCallback callback; @Override public void sendChangesetZip(int resourceId, long zipSize, InputStream zipStream) { this.resourceId = resourceId; fileSize = zipSize; inputStream = zipStream; if (callback != null) { callback.execute(); } } @Override public void sendFilesZip(int resourceId, String driftDefName, String token, long zipSize, InputStream zipStream) { this.resourceId = resourceId; this.driftDefName = driftDefName; this.token = token; fileSize = zipSize; inputStream = zipStream; if (callback != null) { callback.execute(); } } @Override public void repeatChangeSet(int resourceId, String driftDefName, int version) { } @Override public Map<Integer, List<DriftDefinition>> getDriftDefinitions(Set<Integer> resourceIds) { return null; } @Override public DriftSnapshot getCurrentSnapshot(int driftDefinitionId) { return null; } @Override public DriftSnapshot getSnapshot(int driftDefinitionId, int startVersion, int endVersion) { return null; } @Override public void updateCompliance(int resourceId, String drfitDefName, DriftComplianceStatus complianceStatus) { } } /** * This callback interface provides a hook for doing any verification immediately after * DriftManager calls DriftServerService. */ private static interface DriftServiceCallback { void execute(); } private class TestDriftManager extends DriftManager { public TestDriftManager(PluginContainerConfiguration configuration) { super(configuration, null, new FakeInventoryManager(configuration)); } } private class FakeInventoryManager extends InventoryManager { private final Map<Integer, ResourceContainer> resourceContainers = new HashMap<Integer, ResourceContainer>(); public FakeInventoryManager(PluginContainerConfiguration configuration) { super(configuration, null, pluginManager); initialize(); } @Override public ResourceContainer getResourceContainer(int resourceId) { ResourceContainer container = resourceContainers.get(resourceId); if (container == null) { Resource resource = new Resource(); resource.setId(resourceId); container = new ResourceContainer(resource, getClass().getClassLoader()); resourceContainers.put(resourceId, container); } return container; } } }