/*
* RHQ Management Platform
* Copyright (C) 2005-2008 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.server.plugins.drift.mongodb;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.InputStream;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.jmock.Expectations;
import org.rhq.common.drift.ChangeSetWriter;
import org.rhq.common.drift.ChangeSetWriterImpl;
import org.rhq.common.drift.FileEntry;
import org.rhq.common.drift.Headers;
import org.rhq.core.clientapi.agent.drift.DriftAgentService;
import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.criteria.GenericDriftChangeSetCriteria;
import org.rhq.core.domain.criteria.GenericDriftCriteria;
import org.rhq.core.domain.drift.Drift;
import org.rhq.core.domain.drift.DriftChangeSet;
import org.rhq.core.domain.drift.DriftDefinition;
import org.rhq.core.domain.drift.DriftFile;
import org.rhq.core.domain.drift.dto.DriftChangeSetDTO;
import org.rhq.core.domain.drift.dto.DriftDTO;
import org.rhq.core.domain.drift.dto.DriftFileDTO;
import org.rhq.core.domain.util.PageList;
import org.rhq.core.util.MessageDigestGenerator;
import org.rhq.core.util.ZipUtil;
import org.rhq.core.util.file.FileUtil;
import org.rhq.core.util.stream.StreamUtil;
import org.rhq.enterprise.server.plugin.pc.drift.DriftChangeSetSummary;
import org.rhq.enterprise.server.plugins.drift.mongodb.dao.ChangeSetDAO;
import org.rhq.enterprise.server.plugins.drift.mongodb.dao.FileDAO;
import org.rhq.enterprise.server.plugins.drift.mongodb.entities.MongoDBChangeSet;
import org.rhq.enterprise.server.plugins.drift.mongodb.entities.MongoDBChangeSetEntry;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import static java.util.Arrays.asList;
import static org.apache.commons.io.IOUtils.write;
import static org.rhq.core.domain.drift.DriftCategory.FILE_ADDED;
import static org.rhq.core.domain.drift.DriftCategory.FILE_CHANGED;
import static org.rhq.core.domain.drift.DriftCategory.FILE_REMOVED;
import static org.rhq.core.domain.drift.DriftChangeSetCategory.COVERAGE;
import static org.rhq.core.domain.drift.DriftChangeSetCategory.DRIFT;
import static org.rhq.core.domain.drift.DriftConfigurationDefinition.DriftHandlingMode.normal;
import static org.rhq.core.domain.drift.DriftConfigurationDefinition.DriftHandlingMode.plannedChanges;
import static org.rhq.test.AssertUtils.assertPropertiesMatch;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
public class MongoDBDriftServerTest extends MongoDBTest {
private MessageDigestGenerator digestGenerator;
private Random random;
private TestMongoDBDriftServer driftServer;
private ChangeSetDAO changeSetDAO;
@BeforeClass
public void initClass() throws Exception {
digestGenerator = new MessageDigestGenerator(MessageDigestGenerator.SHA_256);
random = new Random();
}
@AfterClass
public void cleanUp() throws Exception {
File tmpDir = new File(System.getProperty("java.io.tmpdir"));
File[] dirs = tmpDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith("changeset_content");
}
});
if (dirs != null) {
for (File dir : dirs) {
FileUtil.purge(dir, true);
}
}
}
@BeforeMethod
public void initTest() {
clearCollections("changesets", "fs.files", "fs.chunks");
File basedir = getBaseDir();
FileUtil.purge(basedir, true);
getBaseDir().mkdirs();
changeSetDAO = new ChangeSetDAO(morphia, connection, "rhqtest");
driftServer = new TestMongoDBDriftServer();
driftServer.setConnection(connection);
driftServer.setMorphia(morphia);
driftServer.setDatastore(ds);
driftServer.setChangeSetDAO(changeSetDAO);
driftServer.setFileDAO(new FileDAO(ds.getDB()));
}
@Test
public void persistInitialChangeSetWithContentNotInDB() throws Exception {
int driftDefId = 1;
final String driftDefName = "saveInitialChangeSetWithContentNotInDB";
final int resourceId = 1;
final Headers headers = new Headers();
headers.setBasedir(getBaseDir().getAbsolutePath());
headers.setDriftDefinitionId(driftDefId);
headers.setDriftDefinitionName(driftDefName);
headers.setResourceId(resourceId);
headers.setType(COVERAGE);
headers.setVersion(0);
String file1SHA = sha256("1a2b3c4d");
String file2SHA = sha256("1a2b3c4d");
File changeSetZip = createChangeSetZipFile(headers,
addedFileEntry("1.bin", file1SHA),
addedFileEntry("2.bin", file2SHA));
final List<? extends DriftFile> missingContent = asList(new TestDriftFile(file1SHA),
new TestDriftFile(file2SHA));
final DriftAgentService driftAgentService = context.mock(DriftAgentService.class);
context.checking(new Expectations() {{
exactly(1).of(driftAgentService).ackChangeSet(resourceId, driftDefName);
// TODO Need to verify that we send the correct headers to the agent
exactly(1).of(driftAgentService).requestDriftFiles(with(resourceId), with(any(Headers.class)),
with(missingContent));
}});
driftServer.setDriftAgentService(driftAgentService);
// We can pass null for the subject because MongoDBDriftServer currently does not
// use the subject argument.
DriftChangeSetSummary actualSummary = driftServer.saveChangeSet(null, resourceId, changeSetZip);
// verify that the change set was persisted
List<MongoDBChangeSet> changeSets = changeSetDAO.find().asList();
assertEquals(changeSets.size(), 1, "Expected to find one change set in the database.");
MongoDBChangeSet actual = changeSets.get(0);
MongoDBChangeSet expected = new MongoDBChangeSet();
// Need to set the id to actual.id. Since ids are random, we cannot use a canned
// value. We have to set it the same value that is in the database.
expected.setId(actual.getId());
expected.setDriftDefinitionId(driftDefId);
expected.setResourceId(resourceId);
expected.setDriftDefinitionName(driftDefName);
expected.setCategory(COVERAGE);
expected.setVersion(0);
expected.setDriftHandlingMode(normal);
MongoDBChangeSetEntry entry1 = new MongoDBChangeSetEntry("1.bin", FILE_ADDED);
entry1.setNewFileHash(file1SHA);
expected.add(entry1);
MongoDBChangeSetEntry entry2 = new MongoDBChangeSetEntry("2.bin", FILE_ADDED);
entry2.setNewFileHash(file2SHA);
expected.add(entry2);
String[] ignore = new String[] {"id", "objectId", "ctime"};
assertChangeSetMatches("Failed to persist change set", expected, actual, ignore);
DriftChangeSetSummary expectedSummary = new DriftChangeSetSummary();
expectedSummary.setCategory(COVERAGE);
expectedSummary.setResourceId(resourceId);
expectedSummary.setDriftDefinitionName(driftDefName);
expectedSummary.setCreatedTime(actual.getCtime());
assertPropertiesMatch("The change set summary is wrong", expectedSummary, actualSummary);
}
@Test
public void persistInitialChangeSetWithContentInDB() throws Exception {
int driftDefId = 1;
final String driftDefName = "saveInitialChangeSetWithContentInDB";
final int resourceId = 1;
final Headers headers = new Headers();
headers.setBasedir(getBaseDir().getAbsolutePath());
headers.setDriftDefinitionId(driftDefId);
headers.setDriftDefinitionName(driftDefName);
headers.setResourceId(resourceId);
headers.setType(COVERAGE);
headers.setVersion(0);
String file1SHA = sha256("1a2b3c4d");
String file2SHA = sha256("1a2b3c4d");
// store content in the database
File file1 = createRandomFile(getBaseDir(), file1SHA, 1024);
File file2 = createRandomFile(getBaseDir(), file2SHA, 1024);
FileDAO fileDAO = new FileDAO(ds.getDB());
fileDAO.save(file1);
fileDAO.save(file2);
File changeSetZip = createChangeSetZipFile(headers,
addedFileEntry("1.bin", file1SHA),
addedFileEntry("2.bin", file2SHA));
final DriftAgentService driftAgentService = context.mock(DriftAgentService.class);
context.checking(new Expectations() {{
exactly(1).of(driftAgentService).ackChangeSet(resourceId, driftDefName);
}});
driftServer.setDriftAgentService(driftAgentService);
// We can pass null for the subject because MongoDBDriftServer currently does not
// use the subject argument.
DriftChangeSetSummary actualSummary = driftServer.saveChangeSet(null, resourceId, changeSetZip);
// verify that the change set was persisted
ChangeSetDAO changeSetDAO = new ChangeSetDAO(morphia, connection, "rhqtest");
List<MongoDBChangeSet> changeSets = changeSetDAO.find().asList();
assertEquals(changeSets.size(), 1, "Expected to find one change set in the database.");
MongoDBChangeSet actual = changeSets.get(0);
MongoDBChangeSet expected = new MongoDBChangeSet();
// Need to set the id to actual.id. Since ids are random, we cannot use a canned
// value. We have to set it the same value that is in the database.
expected.setId(actual.getId());
expected.setDriftDefinitionId(driftDefId);
expected.setResourceId(resourceId);
expected.setDriftDefinitionName(driftDefName);
expected.setCategory(COVERAGE);
expected.setVersion(0);
expected.setDriftHandlingMode(normal);
MongoDBChangeSetEntry entry1 = new MongoDBChangeSetEntry("1.bin", FILE_ADDED);
entry1.setNewFileHash(file1SHA);
expected.add(entry1);
MongoDBChangeSetEntry entry2 = new MongoDBChangeSetEntry("2.bin", FILE_ADDED);
entry2.setNewFileHash(file2SHA);
expected.add(entry2);
String[] ignore = new String[] {"id", "objectId", "ctime"};
assertChangeSetMatches("Failed to persist change set", expected, actual, ignore);
DriftChangeSetSummary expectedSummary = new DriftChangeSetSummary();
expectedSummary.setCategory(COVERAGE);
expectedSummary.setResourceId(resourceId);
expectedSummary.setDriftDefinitionName(driftDefName);
expectedSummary.setCreatedTime(actual.getCtime());
assertPropertiesMatch("The change set summary is wrong", expectedSummary, actualSummary);
}
@Test
public void persistChangeSetWithSomeContentInDB() throws Exception {
int driftDefId = 1;
final String driftDefName = "saveChangeSetWithSomeContentInDB";
final int resourceId = 1;
final Headers headers = new Headers();
headers.setBasedir(getBaseDir().getAbsolutePath());
headers.setDriftDefinitionId(driftDefId);
headers.setDriftDefinitionName(driftDefName);
headers.setResourceId(resourceId);
headers.setType(DRIFT);
headers.setVersion(1);
String oldFile1SHA = sha256("1a2b3c4d");
String newFile1SHA = sha256("2a3b4c5d");
String file2SHA = sha256("1a2b3c4d");
// store content in the database
File oldFile1 = createRandomFile(getBaseDir(), oldFile1SHA, 1024);
File file2 = createRandomFile(getBaseDir(), file2SHA, 1024);
FileDAO fileDAO = new FileDAO(ds.getDB());
fileDAO.save(oldFile1);
fileDAO.save(file2);
File changeSetZip = createChangeSetZipFile(headers,
changedFileEntry("1.bin", oldFile1SHA, newFile1SHA),
removedFileEntry("2.bin", file2SHA));
final List<? extends DriftFile> missingContent = asList(new TestDriftFile(newFile1SHA));
final DriftAgentService driftAgentService = context.mock(DriftAgentService.class);
context.checking(new Expectations() {{
exactly(1).of(driftAgentService).ackChangeSet(resourceId, driftDefName);
exactly(1).of(driftAgentService).requestDriftFiles(with(resourceId), with(any(Headers.class)),
with(missingContent));
}});
driftServer.setDriftAgentService(driftAgentService);
// We can pass null for the subject because MongoDBDriftServer currently does not
// use the subject argument.
DriftChangeSetSummary actualSummary = driftServer.saveChangeSet(null, resourceId, changeSetZip);
// verify that the change set was persisted
ChangeSetDAO changeSetDAO = new ChangeSetDAO(morphia, connection, "rhqtest");
List<MongoDBChangeSet> changeSets = changeSetDAO.find().asList();
assertEquals(changeSets.size(), 1, "Expected to find one change set in the database.");
MongoDBChangeSet actual = changeSets.get(0);
MongoDBChangeSet expected = new MongoDBChangeSet();
// Need to set the id to actual.id. Since ids are random, we cannot use a canned
// value. We have to set it the same value that is in the database.
expected.setId(actual.getId());
expected.setDriftDefinitionId(driftDefId);
expected.setResourceId(resourceId);
expected.setDriftDefinitionName(driftDefName);
expected.setCategory(DRIFT);
expected.setVersion(1);
expected.setDriftHandlingMode(normal);
MongoDBChangeSetEntry entry1 = new MongoDBChangeSetEntry("1.bin", FILE_CHANGED);
entry1.setOldFileHash(oldFile1SHA);
entry1.setNewFileHash(newFile1SHA);
expected.add(entry1);
MongoDBChangeSetEntry entry2 = new MongoDBChangeSetEntry("2.bin", FILE_REMOVED);
entry2.setOldFileHash(file2SHA);
expected.add(entry2);
String[] ignore = new String[] {"id", "objectId", "ctime"};
assertChangeSetMatches("Failed to persist change set", expected, actual, ignore);
DriftChangeSetSummary expectedSummary = new DriftChangeSetSummary();
expectedSummary.setCategory(DRIFT);
expectedSummary.setResourceId(resourceId);
expectedSummary.setDriftDefinitionName(driftDefName);
expectedSummary.setCreatedTime(actual.getCtime());
expectedSummary.addDriftPathname("1.bin");
expectedSummary.addDriftPathname("2.bin");
assertPropertiesMatch("The change set summary is wrong", expectedSummary, actualSummary);
}
@Test
public void persistChangeSetFileContent() throws Exception {
int size = 1024;
File file1 = createRandomFile(getBaseDir(), size);
File file2 = createRandomFile(getBaseDir(), size);
driftServer.saveChangeSetFiles(null, createChangeSetContentZipFile(file1, file2));
FileDAO fileDAO = new FileDAO(ds.getDB());
for (File expectedFile : asList(file1, file2)) {
InputStream inputStream = fileDAO.findOne(expectedFile.getName());
assertNotNull(inputStream, "Failed to find file in database with id " + expectedFile.getName());
File actualFile = new File(getBaseDir(), "actualContent");
actualFile.delete();
StreamUtil.copy(inputStream, new FileOutputStream(actualFile));
assertEquals(sha256(actualFile), sha256(expectedFile), "The SHA-256 hash in the database does not " +
"match that of " + expectedFile.getPath());
}
}
@Test
public void purgeOrphanedContent() throws Exception {
int size = 1024;
File file1 = createRandomFile(getBaseDir(), size);
File file2 = createRandomFile(getBaseDir(), size);
driftServer.saveChangeSetFiles(null, createChangeSetContentZipFile(file1, file2));
MongoDBChangeSet changeSet = new MongoDBChangeSet();
changeSet.setDriftDefinitionId(1);
changeSet.setResourceId(1);
changeSet.setDriftDefinitionName("testdef");
changeSet.setCategory(COVERAGE);
changeSet.setVersion(0);
MongoDBChangeSetEntry entry = new MongoDBChangeSetEntry("./1.bin", FILE_ADDED);
entry.setNewFileHash(file1.getName());
changeSet.add(entry);
changeSetDAO.save(changeSet);
driftServer.purgeOrphanedContent();
FileDAO fileDAO = new FileDAO(ds.getDB());
assertNull(fileDAO.findById(file2.getName()), "Expected unreferenced file to be deleted");
assertNotNull(fileDAO.findById(file1.getName()), "Referenced file should not be deleted");
}
@Test
public void findChangeSetsByCriteriaAndDoNotFetchDrifts() throws Exception {
String driftDefName = "testdef";
int driftDefId = 1;
int resourceId1 = 1;
int resourceId2 = 2;
MongoDBChangeSet changeSet1 = new MongoDBChangeSet();
changeSet1.setDriftDefinitionId(driftDefId);
changeSet1.setDriftDefinitionName(driftDefName);
changeSet1.setResourceId(resourceId1);
changeSet1.setCategory(COVERAGE);
changeSet1.setVersion(0);
MongoDBChangeSetEntry entry1 = new MongoDBChangeSetEntry("./1.bin", FILE_ADDED);
entry1.setNewFileHash("./1.bin");
changeSet1.add(entry1);
changeSetDAO.save(changeSet1);
MongoDBChangeSet changeSet2 = new MongoDBChangeSet();
changeSet2.setDriftDefinitionId(driftDefId);
changeSet2.setDriftDefinitionName(driftDefName);
changeSet2.setResourceId(resourceId1);
changeSet2.setCategory(DRIFT);
changeSet2.setVersion(1);
MongoDBChangeSetEntry entry2 = new MongoDBChangeSetEntry("./1.bin", FILE_CHANGED);
entry2.setNewFileHash(sha256("./1.bin.new"));
entry2.setOldFileHash(sha256("./1.bin"));
changeSet2.add(entry2);
MongoDBChangeSetEntry entry3 = new MongoDBChangeSetEntry("./2.bin", FILE_ADDED);
entry3.setNewFileHash("./2.bin");
changeSet2.add(entry3);
changeSetDAO.save(changeSet2);
MongoDBChangeSet changeSet3 = new MongoDBChangeSet();
changeSet3.setDriftDefinitionId(driftDefId);
changeSet3.setDriftDefinitionName(driftDefName);
changeSet3.setResourceId(resourceId2);
changeSet3.setCategory(COVERAGE);
changeSet3.setVersion(0);
MongoDBChangeSetEntry entry4 = new MongoDBChangeSetEntry("./3.bin", FILE_ADDED);
entry4.setNewFileHash(sha256("./3.bin"));
changeSet3.add(entry4);
changeSetDAO.save(changeSet3);
GenericDriftChangeSetCriteria criteria = new GenericDriftChangeSetCriteria();
criteria.addFilterDriftDefinitionId(driftDefId);
criteria.addFilterResourceId(resourceId1);
criteria.addFilterVersion("1");
Mapper mapper = new Mapper();
DriftChangeSetDTO expected = mapper.toDTO(changeSet2);
PageList<? extends DriftChangeSet<?>> actualChangeSets = driftServer.findDriftChangeSetsByCriteria(null,
criteria);
assertEquals(actualChangeSets.size(), 1, "Expected to get back one change set.");
DriftChangeSetDTO actual = (DriftChangeSetDTO) actualChangeSets.get(0);
assertChangeSetMatches("Failed to return change set DTO", expected, actual);
assertTrue(actual.getDrifts().isEmpty(), "Drifts should not have been returned since criteria did not " +
"specify to fetch drifts");
}
@Test
public void findChangeSetsByCriteriaAndFetchDrifts() throws Exception {
String driftDefName = "testdef";
int driftDefId = 1;
int resourceId1 = 1;
int resourceId2 = 2;
MongoDBChangeSet changeSet1 = new MongoDBChangeSet();
changeSet1.setDriftDefinitionId(driftDefId);
changeSet1.setDriftDefinitionName(driftDefName);
changeSet1.setResourceId(resourceId1);
changeSet1.setCategory(COVERAGE);
changeSet1.setVersion(0);
MongoDBChangeSetEntry entry1 = new MongoDBChangeSetEntry("./1.bin", FILE_ADDED);
entry1.setNewFileHash("./1.bin");
changeSet1.add(entry1);
changeSetDAO.save(changeSet1);
MongoDBChangeSet changeSet2 = new MongoDBChangeSet();
changeSet2.setDriftDefinitionId(driftDefId);
changeSet2.setDriftDefinitionName(driftDefName);
changeSet2.setResourceId(resourceId1);
changeSet2.setCategory(DRIFT);
changeSet2.setVersion(1);
MongoDBChangeSetEntry entry2 = new MongoDBChangeSetEntry("./1.bin", FILE_CHANGED);
entry2.setNewFileHash(sha256("./1.bin.new"));
entry2.setOldFileHash(sha256("./1.bin"));
changeSet2.add(entry2);
MongoDBChangeSetEntry entry3 = new MongoDBChangeSetEntry("./2.bin", FILE_ADDED);
entry3.setNewFileHash("./2.bin");
changeSet2.add(entry3);
changeSetDAO.save(changeSet2);
MongoDBChangeSet changeSet3 = new MongoDBChangeSet();
changeSet3.setDriftDefinitionId(driftDefId);
changeSet3.setDriftDefinitionName(driftDefName);
changeSet3.setResourceId(resourceId2);
changeSet3.setCategory(COVERAGE);
changeSet3.setVersion(0);
MongoDBChangeSetEntry entry4 = new MongoDBChangeSetEntry("./3.bin", FILE_ADDED);
entry4.setNewFileHash(sha256("./3.bin"));
changeSet3.add(entry4);
changeSetDAO.save(changeSet3);
GenericDriftChangeSetCriteria criteria = new GenericDriftChangeSetCriteria();
criteria.addFilterDriftDefinitionId(driftDefId);
criteria.addFilterResourceId(resourceId1);
criteria.addFilterVersion("1");
criteria.fetchDrifts(true);
Mapper mapper = new Mapper();
DriftChangeSetDTO expected = mapper.toDTO(changeSet2);
PageList<? extends DriftChangeSet<?>> actualChangeSets = driftServer.findDriftChangeSetsByCriteria(null,
criteria);
assertEquals(actualChangeSets.size(), 1, "Expected to get back one change set.");
DriftChangeSetDTO actual = (DriftChangeSetDTO) actualChangeSets.get(0);
assertChangeSetMatches("Failed to return change set DTO", expected, actual);
assertEquals(actual.getDrifts().size(), 2, "Expected change set to contain two drifts");
assertDriftMatches("The drift should have been included in the change set", mapper.toDTO(entry2),
find(actual.getDrifts(), "./1.bin"));
assertDriftMatches("The drift should have been included in the change set", mapper.toDTO(entry3),
find(actual.getDrifts(), "./2.bin"));
}
@Test
public void findDriftsByCriteriaAndDoNotFetchChangeSets() {
String driftDefName = "testdef";
int driftDefId = 1;
int resourceId1 = 1;
int resourceId2 = 2;
MongoDBChangeSet changeSet1 = new MongoDBChangeSet();
changeSet1.setDriftDefinitionId(driftDefId);
changeSet1.setDriftDefinitionName(driftDefName);
changeSet1.setResourceId(resourceId1);
changeSet1.setCategory(COVERAGE);
changeSet1.setVersion(0);
MongoDBChangeSetEntry entry1 = new MongoDBChangeSetEntry("./1.bin", FILE_ADDED);
entry1.setNewFileHash("./1.bin");
changeSet1.add(entry1);
changeSetDAO.save(changeSet1);
MongoDBChangeSet changeSet2 = new MongoDBChangeSet();
changeSet2.setDriftDefinitionId(driftDefId);
changeSet2.setDriftDefinitionName(driftDefName);
changeSet2.setResourceId(resourceId1);
changeSet2.setCategory(DRIFT);
changeSet2.setVersion(1);
MongoDBChangeSetEntry entry2 = new MongoDBChangeSetEntry("./1.bin", FILE_CHANGED);
entry2.setNewFileHash(sha256("./1.bin.new"));
entry2.setOldFileHash(sha256("./1.bin"));
changeSet2.add(entry2);
MongoDBChangeSetEntry entry3 = new MongoDBChangeSetEntry("./2.bin", FILE_ADDED);
entry3.setNewFileHash("./2.bin");
changeSet2.add(entry3);
changeSetDAO.save(changeSet2);
MongoDBChangeSet changeSet3 = new MongoDBChangeSet();
changeSet3.setDriftDefinitionId(driftDefId);
changeSet3.setDriftDefinitionName(driftDefName);
changeSet3.setResourceId(resourceId2);
changeSet3.setCategory(COVERAGE);
changeSet3.setVersion(0);
MongoDBChangeSetEntry entry4 = new MongoDBChangeSetEntry("./3.bin", FILE_ADDED);
entry4.setNewFileHash(sha256("./3.bin"));
changeSet3.add(entry4);
changeSetDAO.save(changeSet3);
GenericDriftCriteria criteria = new GenericDriftCriteria();
criteria.addFilterResourceIds(resourceId1);
criteria.addFilterDriftDefinitionId(driftDefId);
criteria.addFilterCategories(FILE_CHANGED);
criteria.addFilterChangeSetStartVersion(1);
PageList<? extends Drift<?, ?>> actualDrifts = driftServer.findDriftsByCriteria(null, criteria);
assertEquals(actualDrifts.size(), 1, "Expected to get back one drift");
DriftDTO actual = (DriftDTO) actualDrifts.get(0);
Mapper mapper = new Mapper();
DriftDTO expected = mapper.toDTO(entry2);
assertDriftMatches("Failed to return drift DTO", expected, actual);
assertNull(actual.getChangeSet(), "The change set should not have been included with the returned drift");
}
@Test
public void findDriftsByCriteriaAndFetchChangeSets() {
String driftDefName = "testdef";
int driftDefId = 1;
int resourceId1 = 1;
int resourceId2 = 2;
MongoDBChangeSet changeSet1 = new MongoDBChangeSet();
changeSet1.setDriftDefinitionId(driftDefId);
changeSet1.setDriftDefinitionName(driftDefName);
changeSet1.setResourceId(resourceId1);
changeSet1.setCategory(COVERAGE);
changeSet1.setVersion(0);
MongoDBChangeSetEntry entry1 = new MongoDBChangeSetEntry("./1.bin", FILE_ADDED);
entry1.setNewFileHash("./1.bin");
changeSet1.add(entry1);
changeSetDAO.save(changeSet1);
MongoDBChangeSet changeSet2 = new MongoDBChangeSet();
changeSet2.setDriftDefinitionId(driftDefId);
changeSet2.setDriftDefinitionName(driftDefName);
changeSet2.setResourceId(resourceId1);
changeSet2.setCategory(DRIFT);
changeSet2.setVersion(1);
MongoDBChangeSetEntry entry2 = new MongoDBChangeSetEntry("./1.bin", FILE_CHANGED);
entry2.setNewFileHash(sha256("./1.bin.new"));
entry2.setOldFileHash(sha256("./1.bin"));
changeSet2.add(entry2);
MongoDBChangeSetEntry entry3 = new MongoDBChangeSetEntry("./2.bin", FILE_ADDED);
entry3.setNewFileHash("./2.bin");
changeSet2.add(entry3);
changeSetDAO.save(changeSet2);
MongoDBChangeSet changeSet3 = new MongoDBChangeSet();
changeSet3.setDriftDefinitionId(driftDefId);
changeSet3.setDriftDefinitionName(driftDefName);
changeSet3.setResourceId(resourceId2);
changeSet3.setCategory(COVERAGE);
changeSet3.setVersion(0);
MongoDBChangeSetEntry entry4 = new MongoDBChangeSetEntry("./3.bin", FILE_ADDED);
entry4.setNewFileHash(sha256("./3.bin"));
changeSet3.add(entry4);
changeSetDAO.save(changeSet3);
GenericDriftCriteria criteria = new GenericDriftCriteria();
criteria.addFilterResourceIds(resourceId1);
criteria.addFilterDriftDefinitionId(driftDefId);
criteria.addFilterCategories(FILE_CHANGED);
criteria.addFilterChangeSetStartVersion(1);
criteria.fetchChangeSet(true);
PageList<? extends Drift<?, ?>> actualDrifts = driftServer.findDriftsByCriteria(null, criteria);
assertEquals(actualDrifts.size(), 1, "Expected to get back one drift");
DriftDTO actual = (DriftDTO) actualDrifts.get(0);
Mapper mapper = new Mapper();
DriftDTO expected = mapper.toDTO(entry2);
assertDriftMatches("Failed to return drift DTO", expected, actual);
assertChangeSetMatches("The change set should have been fetched", mapper.toDTO(changeSet2),
actual.getChangeSet());
}
@Test
public void persistNewInitialChangeSetForPinnedDriftDef() throws Exception {
DriftDefinition driftDef = new DriftDefinition(new Configuration());
driftDef.setId(1);
driftDef.setName("testdef");
// This is essentially a test hook. MongoDBDriftServer.persistChangeSet has to call
// back into DriftManagerBean to look up the drift definition to get the definition
// name. Setting the driftDef property here allows us to avoid introducing EJB into
// the test.
driftServer.driftDef = driftDef;
int resourceId = 1;
// First create the change set from which the pinned change set will be created
DriftChangeSetDTO changeSetDTO = new DriftChangeSetDTO();
changeSetDTO.setResourceId(resourceId);
changeSetDTO.setDriftDefinitionId(driftDef.getId());
changeSetDTO.setDriftHandlingMode(plannedChanges);
changeSetDTO.setCategory(COVERAGE);
changeSetDTO.setCtime(new Date().getTime());
changeSetDTO.setVersion(0);
Set<DriftDTO> driftDTOs = new HashSet<DriftDTO>();
DriftDTO drift1DTO = new DriftDTO();
drift1DTO.setChangeSet(changeSetDTO);
drift1DTO.setCategory(FILE_ADDED);
drift1DTO.setPath("./1.bin");
drift1DTO.setDirectory(".");
drift1DTO.setCtime(new Date().getTime());
drift1DTO.setNewDriftFile(newDriftFile(sha256("./1.bin")));
driftDTOs.add(drift1DTO);
DriftDTO drift2DTO = new DriftDTO();
drift2DTO.setChangeSet(changeSetDTO);
drift2DTO.setCategory(FILE_ADDED);
drift2DTO.setPath("./2.bin");
drift2DTO.setDirectory(".");
drift2DTO.setCtime(new Date().getTime());
drift2DTO.setNewDriftFile(newDriftFile(sha256("./2.bin")));
driftDTOs.add(drift2DTO);
changeSetDTO.setDrifts(driftDTOs);
driftServer.persistChangeSet(null, changeSetDTO);
GenericDriftChangeSetCriteria criteria = new GenericDriftChangeSetCriteria();
criteria.addFilterDriftDefinitionId(driftDef.getId());
criteria.fetchDrifts(true);
List<MongoDBChangeSet> changeSets = changeSetDAO.findByChangeSetCritiera(criteria);
assertEquals(changeSets.size(), 1, "There should only be one change set for drift definition id " +
driftDef.getId());
Mapper mapper = new Mapper();
DriftChangeSetDTO actualChangeSet = mapper.toDTO(changeSets.get(0));
Set<DriftDTO> actualDrifts = new HashSet<DriftDTO>();
for (MongoDBChangeSetEntry entry : changeSets.get(0).getDrifts()) {
actualDrifts.add(mapper.toDTO(entry));
}
assertChangeSetMatches("Failed to persist change set", changeSetDTO, actualChangeSet);
assertDriftMatches("The drift should have been persisted in the change set", drift1DTO,
find(actualDrifts, "./1.bin"));
assertDriftFileMatches("Failed to persist the drift file data", drift1DTO.getNewDriftFile(),
find(actualDrifts, "./1.bin").getNewDriftFile());
assertDriftMatches("The drift should have been persisted in the change set", drift2DTO,
find(actualDrifts, "./2.bin"));
assertDriftFileMatches("Failed to persist the drift file data", drift2DTO.getNewDriftFile(),
find(actualDrifts, "./2.bin").getNewDriftFile());
}
@Test
public void persistNewChangeSetForPinnedTemplate() throws Exception {
// First create the change set from which the pinned change set will be created
DriftChangeSetDTO changeSetDTO = new DriftChangeSetDTO();
changeSetDTO.setDriftHandlingMode(plannedChanges);
changeSetDTO.setCategory(COVERAGE);
changeSetDTO.setCtime(new Date().getTime());
changeSetDTO.setVersion(0);
Set<DriftDTO> driftDTOs = new HashSet<DriftDTO>();
DriftDTO drift1DTO = new DriftDTO();
drift1DTO.setChangeSet(changeSetDTO);
drift1DTO.setCategory(FILE_ADDED);
drift1DTO.setPath("./1.bin");
drift1DTO.setDirectory(".");
drift1DTO.setCtime(new Date().getTime());
drift1DTO.setNewDriftFile(newDriftFile(sha256("./1.bin")));
driftDTOs.add(drift1DTO);
DriftDTO drift2DTO = new DriftDTO();
drift2DTO.setChangeSet(changeSetDTO);
drift2DTO.setCategory(FILE_ADDED);
drift2DTO.setPath("./2.bin");
drift2DTO.setDirectory(".");
drift2DTO.setCtime(new Date().getTime());
drift2DTO.setNewDriftFile(newDriftFile(sha256("./2.bin")));
driftDTOs.add(drift2DTO);
changeSetDTO.setDrifts(driftDTOs);
driftServer.persistChangeSet(null, changeSetDTO);
GenericDriftChangeSetCriteria criteria = new GenericDriftChangeSetCriteria();
criteria.fetchDrifts(true);
List<MongoDBChangeSet> changeSets = changeSetDAO.findByChangeSetCritiera(criteria);
assertEquals(changeSets.size(), 1, "There should only be one change set.");
Mapper mapper = new Mapper();
DriftChangeSetDTO actualChangeSet = mapper.toDTO(changeSets.get(0));
Set<DriftDTO> actualDrifts = new HashSet<DriftDTO>();
for (MongoDBChangeSetEntry entry : changeSets.get(0).getDrifts()) {
actualDrifts.add(mapper.toDTO(entry));
}
assertChangeSetMatches("Failed to persist change set", changeSetDTO, actualChangeSet);
assertDriftMatches("The drift should have been persisted in the change set", drift1DTO,
find(actualDrifts, "./1.bin"));
assertDriftFileMatches("Failed to persist the drift file data", drift1DTO.getNewDriftFile(),
find(actualDrifts, "./1.bin").getNewDriftFile());
assertDriftMatches("The drift should have been persisted in the change set", drift2DTO,
find(actualDrifts, "./2.bin"));
assertDriftFileMatches("Failed to persist the drift file data", drift2DTO.getNewDriftFile(),
find(actualDrifts, "./2.bin").getNewDriftFile());
}
/**
* Performs a property-wise comparison of the change set DTOs. The id, ctime, and drift
* properties are excluded from comparison.
*
* @param msg An error message
* @param expected
* @param actual
*/
private void assertChangeSetMatches(String msg, DriftChangeSetDTO expected, DriftChangeSetDTO actual) {
assertPropertiesMatch(msg + ": " + DriftChangeSetDTO.class.getSimpleName() + " objects do not match.",
expected, actual, "id", "ctime", "drifts");
}
/**
* Performs a property-wise comparison of the drift DTOs. The id, ctime, changeSet,
* oldDriftFile, and newDriftFile properties are excluded from comparison. The
* changeSet, oldDriftFile, and newDriftFile properties should be verified using the
* {@link #assertChangeSetMatches(String, DriftChangeSetDTO, DriftChangeSetDTO)} and
* the {@link #assertDriftFileMatches(String, DriftFileDTO, DriftFileDTO)} methods
* respectively.
*
* @param msg
* @param expected
* @param actual
*/
private void assertDriftMatches(String msg, DriftDTO expected, DriftDTO actual) {
assertPropertiesMatch(msg + ": " + DriftDTO.class.getSimpleName() + " objects do not match.",
expected, actual, "id", "ctime", "changeSet", "oldDriftFile", "newDriftFile");
}
/**
* Performs a property-wise comparison of the drift file DTOs.
*
* @param msg
* @param expected
* @param actual
*/
private void assertDriftFileMatches(String msg, DriftFileDTO expected, DriftFileDTO actual) {
assertPropertiesMatch(msg + ": " + DriftFileDTO.class.getSimpleName() + " objects do not match.", expected,
actual);
}
/**
* Searches the collection of drifts for one with a matching path. The first match
* found is returned. An assertion error is thrown if no match is found.
*
* @param drifts
* @param path
* @param <D>
* @return
*/
private <D extends Drift<?, ?>> D find(Collection<D> drifts, String path) {
D match = null;
for (D drift : drifts) {
if (path.equals(drift.getPath())) {
match = drift;
break;
}
}
assertNotNull(match, "Failed to find drift with path [" + path + "]");
return match;
}
/**
* Generates a change set zip file. This zip file contains a single entry, the change
* set report or meta data. The file is named changeset.zip and is written to
* {@link #getBaseDir() basedir}.
*
* @param headers The change set headers
* @param fileEntries The entries that will comprise this change set
* @return The zip file as a {@link File} object
* @throws Exception
*/
protected File createChangeSetZipFile(Headers headers, FileEntry... fileEntries) throws Exception {
ChangeSetWriter writer = newChangeSetWriter(headers);
for (FileEntry entry : fileEntries) {
writer.write(entry);
}
writer.close();
File zipFile = new File(getBaseDir(), "changeset.zip");
ZipUtil.zipFileOrDirectory(getChangeSetFile(), zipFile);
return zipFile;
}
/**
* Generates a change set content zip file. This zip file contains the bits of each of
* the specified file. The zip file is named changeset_content_<timestamp>.zip
* and is written to {@link #getBaseDir() basedir}.
*
* @param files The files to include in the content zip file
* @return The zip file as a {@link File} object
* @throws Exception
*/
protected File createChangeSetContentZipFile(File... files) throws Exception {
long timestamp = System.currentTimeMillis();
File contentDir = new File(getBaseDir(), "content_" + timestamp);
contentDir.mkdirs();
File zipFile = new File(getBaseDir(), "changeset_content_" + timestamp + ".zip");
ZipOutputStream zipStream = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));
for (File file : files) {
FileInputStream fis = new FileInputStream(file);
zipStream.putNextEntry(new ZipEntry(file.getName()));
StreamUtil.copy(fis, zipStream, false);
fis.close();
}
zipStream.close();
return zipFile;
}
/**
* Initializes a {@link ChangeSetWriter} that will write to a file returned from
* {@link #getChangeSetFile()}
*
* @return A new {@link ChangeSetWriter}
*/
protected ChangeSetWriter newChangeSetWriter(Headers headers) throws Exception {
return new ChangeSetWriterImpl(getChangeSetFile(), headers);
}
/**
* @return A file named changeset.txt located in {@link #getBaseDir() basedir}
*/
protected File getChangeSetFile() {
return new File(getBaseDir(), "changeset.txt");
}
/**
* Returns a File object that denotes the base directory for this test class. That
* directory by default is <project_basedir>target/<test_class_name>
*
* @return The base directory for this test class
*/
protected File getBaseDir() {
return new File("target", getClass().getSimpleName());
}
protected FileEntry addedFileEntry(String path, String sha256) {
long timestamp = System.currentTimeMillis();
long size = 1024;
return FileEntry.addedFileEntry(path, sha256, timestamp, size);
}
protected FileEntry changedFileEntry(String path, String oldSHA, String newSHA) {
long timestamp = System.currentTimeMillis();
long size = 1024;
return FileEntry.changedFileEntry(path, oldSHA, newSHA, timestamp, size);
}
protected FileEntry removedFileEntry(String path, String oldSHA) {
return FileEntry.removedFileEntry(path, oldSHA);
}
protected String sha256(String string) {
return digestGenerator.calcDigestString(string);
}
protected String sha256(byte[] bytes) throws Exception {
return digestGenerator.calcDigestString(bytes);
}
protected String sha256(File file) throws Exception {
return digestGenerator.calcDigestString(file);
}
/**
* Generates a file of random bytes.
*
* @param dir The directory to which the file will be written
* @param fileName The name of the file to be created
* @param numBytes The size of the file in bytes
* @return The generated file as a {@link File} object
* @throws Exception
*/
protected File createRandomFile(File dir, String fileName, int numBytes) throws Exception {
File file = new File(dir, fileName);
FileOutputStream stream = new FileOutputStream(file);
byte[] bytes = new byte[numBytes];
random.nextBytes(bytes);
write(bytes, stream);
stream.close();
return file;
}
/**
* Generates a file of random bytes where the name of the file is the SHA-256 hash of
* those bytes.
*
* @param dir The directory to which the file will be written
* @param numBytes The size of the file in bytes
* @return The generated file as a {@link File} object
* @throws Exception
*/
protected File createRandomFile(File dir, int numBytes) throws Exception {
byte[] bytes = new byte[numBytes];
random.nextBytes(bytes);
File file = new File(dir, sha256(bytes));
FileOutputStream stream = new FileOutputStream(file);
write(bytes, stream);
stream.close();
return file;
}
private DriftFileDTO newDriftFile(String hash) {
DriftFileDTO file = new DriftFileDTO();
file.setHashId(hash);
return file;
}
private static class TestMongoDBDriftServer extends MongoDBDriftServer {
public DriftAgentService driftAgentService;
public DriftDefinition driftDef;
@Override
public DriftAgentService getDriftAgentService(int resourceId) {
return driftAgentService;
}
public void setDriftAgentService(DriftAgentService driftAgentService) {
this.driftAgentService = driftAgentService;
}
@Override
protected DriftDefinition getDriftDef(Subject subject, int id) {
return driftDef;
}
}
/**
* {@link DriftFileDTO} does not implement equals/hashCode which makes some of the
* verification a little tricky in situations where collections of DriftFile objects
* are getting passed around. This subclass implements equals and hashCode.
*/
private static class TestDriftFile extends DriftFileDTO {
public TestDriftFile(String hash) {
super();
setHashId(hash);
}
/**
* Equality is based soley on the {@link #getHashId() hashId} property.
*
* @param o The object to compare against
* @return true if the object is a TestDriftFile and has the same hashId.
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof DriftFileDTO)) return false;
DriftFileDTO that = (DriftFileDTO) o;
if (!getHashId().equals(that.getHashId())) return false;
return true;
}
/**
* @return A hash code based on the {@link #getHashId() hashId} property.
*/
@Override
public int hashCode() {
return getHashId().hashCode();
}
}
}