package org.epics.archiverappliance.engine.test;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.epics.archiverappliance.SIOCSetup;
import org.epics.archiverappliance.TomcatSetup;
import org.epics.archiverappliance.common.TimeUtils;
import org.epics.archiverappliance.config.ConfigService;
import org.epics.archiverappliance.config.ConfigServiceForTests;
import org.epics.archiverappliance.config.persistence.JDBM2Persistence;
import org.epics.archiverappliance.mgmt.ArchiveWorkflowCompleted;
import org.epics.archiverappliance.retrieval.client.EpicsMessage;
import org.epics.archiverappliance.retrieval.client.GenMsgIterator;
import org.epics.archiverappliance.retrieval.client.RawDataRetrieval;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
/**
* Start an appserver with persistence; start archiving a PV; then start and restart the SIOC and make sure we get the expected cnxlost headers.
* @author mshankar
*
*/
public class CnxLostTest {
private static Logger logger = Logger.getLogger(CnxLostTest.class.getName());
private File persistenceFolder = new File(ConfigServiceForTests.getDefaultPBTestFolder() + File.separator + "CnxLostTest");
TomcatSetup tomcatSetup = new TomcatSetup();
SIOCSetup siocSetup = new SIOCSetup();
WebDriver driver;
@Before
public void setUp() throws Exception {
if(persistenceFolder.exists()) {
FileUtils.deleteDirectory(persistenceFolder);
}
persistenceFolder.mkdirs();
System.getProperties().put(ConfigService.ARCHAPPL_PERSISTENCE_LAYER, "org.epics.archiverappliance.config.persistence.JDBM2Persistence");
System.getProperties().put(JDBM2Persistence.ARCHAPPL_JDBM2_FILENAME, persistenceFolder.getPath() + File.separator + "testconfig.jdbm2");
siocSetup.startSIOCWithDefaultDB();
tomcatSetup.setUpWebApps(this.getClass().getSimpleName());
driver = new FirefoxDriver();
}
@After
public void tearDown() throws Exception {
driver.quit();
tomcatSetup.tearDown();
siocSetup.stopSIOC();
if(persistenceFolder.exists()) {
FileUtils.deleteDirectory(persistenceFolder);
}
File mtsFolder = new File(ConfigServiceForTests.getDefaultPBTestFolder() + File.separator + "UnitTestNoNamingConvention");
if(mtsFolder.exists()) {
FileUtils.deleteDirectory(mtsFolder);
}
}
enum ConnectionLossType {
STARTUP_OR_PAUSE_RESUME,
IOC_RESTART,
NONE
}
class ExpectedEventType {
ConnectionLossType lossType;
int numberOfEvents;
public ExpectedEventType(ConnectionLossType lossType, int numberOfEvents) {
this.lossType = lossType;
this.numberOfEvents = numberOfEvents;
}
}
@Test
public void testConnectionLossHeaders() throws Exception {
driver.get("http://localhost:17665/mgmt/ui/index.html");
WebElement pvstextarea = driver.findElement(By.id("archstatpVNames"));
String pvNameToArchive = "UnitTestNoNamingConvention:inactive1";
pvstextarea.sendKeys(pvNameToArchive);
WebElement archiveButton = driver.findElement(By.id("archstatArchive"));
logger.debug("About to submit");
archiveButton.click();
ArchiveWorkflowCompleted.isArchiveRequestComplete(pvNameToArchive);
WebElement checkStatusButton = driver.findElement(By.id("archstatCheckStatus"));
checkStatusButton.click();
Thread.sleep(2*1000);
WebElement statusPVName = driver.findElement(By.cssSelector("#archstatsdiv_table tr:nth-child(1) td:nth-child(1)"));
String pvNameObtainedFromTable = statusPVName.getText();
assertTrue("PV Name is not " + pvNameToArchive + "; instead we get " + pvNameObtainedFromTable, pvNameToArchive.equals(pvNameObtainedFromTable));
WebElement statusPVStatus = driver.findElement(By.cssSelector("#archstatsdiv_table tr:nth-child(1) td:nth-child(2)"));
String pvArchiveStatusObtainedFromTable = statusPVStatus.getText();
String expectedPVStatus = "Being archived";
assertTrue("Expecting PV archive status to be " + expectedPVStatus + "; instead it is " + pvArchiveStatusObtainedFromTable, expectedPVStatus.equals(pvArchiveStatusObtainedFromTable));
// UnitTestNoNamingConvention:inactive1 is SCAN passive without autosave so it should have an invalid timestamp.
// We caput something to generate a valid timestamp..
siocSetup.caput(pvNameToArchive, "1.0");
Thread.sleep(60*1000);
siocSetup.caput(pvNameToArchive, "2.0");
Thread.sleep(60*1000);
checkRetrieval(pvNameToArchive, new ExpectedEventType[] {
new ExpectedEventType(ConnectionLossType.STARTUP_OR_PAUSE_RESUME, 1),
new ExpectedEventType(ConnectionLossType.NONE, 1)
});
logger.info("We are now archiving the PV; let's go into the details page; pause and resume");
driver.get("http://localhost:17665/mgmt/ui/pvdetails.html?pv=" + pvNameToArchive);
{
Thread.sleep(20*1000);
WebElement pauseArchivingButn = driver.findElement(By.id("pvDetailsPauseArchiving"));
logger.info("Clicking on the button to pause archiving the PV");
pauseArchivingButn.click();
Thread.sleep(20*1000);
WebElement pvDetailsTable = driver.findElement(By.id("pvDetailsTable"));
List<WebElement> pvDetailsTableRows = pvDetailsTable.findElements(By.cssSelector("tbody tr"));
for(WebElement pvDetailsTableRow : pvDetailsTableRows) {
WebElement pvDetailsTableFirstCol = pvDetailsTableRow.findElement(By.cssSelector("td:nth-child(1)"));
if(pvDetailsTableFirstCol.getText().contains("Is this PV paused:")) {
WebElement pvDetailsTableSecondCol = pvDetailsTableRow.findElement(By.cssSelector("td:nth-child(2)"));
String obtainedPauseStatus = pvDetailsTableSecondCol.getText();
String expectedPauseStatus = "Yes";
assertTrue("Expecting paused status to be " + expectedPauseStatus + "; instead it is " + obtainedPauseStatus, expectedPauseStatus.equals(obtainedPauseStatus));
break;
}
}
}
siocSetup.caput(pvNameToArchive, "3.0"); // We are paused; so we should miss this event
Thread.sleep(60*1000);
siocSetup.caput(pvNameToArchive, "4.0");
Thread.sleep(60*1000);
{
Thread.sleep(20*1000);
WebElement resumeArchivingButn = driver.findElement(By.id("pvDetailsResumeArchiving"));
logger.info("Clicking on the button to resume archiving the PV");
resumeArchivingButn.click();
Thread.sleep(20*1000);
WebElement pvDetailsTable = driver.findElement(By.id("pvDetailsTable"));
List<WebElement> pvDetailsTableRows = pvDetailsTable.findElements(By.cssSelector("tbody tr"));
for(WebElement pvDetailsTableRow : pvDetailsTableRows) {
WebElement pvDetailsTableFirstCol = pvDetailsTableRow.findElement(By.cssSelector("td:nth-child(1)"));
if(pvDetailsTableFirstCol.getText().contains("Is this PV paused:")) {
WebElement pvDetailsTableSecondCol = pvDetailsTableRow.findElement(By.cssSelector("td:nth-child(2)"));
String obtainedPauseStatus = pvDetailsTableSecondCol.getText();
String expectedPauseStatus = "No";
assertTrue("Expecting paused status to be " + expectedPauseStatus + "; instead it is " + obtainedPauseStatus, expectedPauseStatus.equals(obtainedPauseStatus));
break;
}
}
}
checkRetrieval(pvNameToArchive, new ExpectedEventType[] {
new ExpectedEventType(ConnectionLossType.STARTUP_OR_PAUSE_RESUME, 1),
new ExpectedEventType(ConnectionLossType.NONE, 1),
new ExpectedEventType(ConnectionLossType.STARTUP_OR_PAUSE_RESUME, 1)
});
siocSetup.stopSIOC();
Thread.sleep(20*1000);
siocSetup = new SIOCSetup();
siocSetup.startSIOCWithDefaultDB();
Thread.sleep(20*1000);
siocSetup.caput(pvNameToArchive, "5.0");
Thread.sleep(60*1000);
siocSetup.caput(pvNameToArchive, "6.0");
Thread.sleep(60*1000);
checkRetrieval(pvNameToArchive, new ExpectedEventType[] {
new ExpectedEventType(ConnectionLossType.STARTUP_OR_PAUSE_RESUME, 1),
new ExpectedEventType(ConnectionLossType.NONE, 1),
new ExpectedEventType(ConnectionLossType.STARTUP_OR_PAUSE_RESUME, 1),
new ExpectedEventType(ConnectionLossType.IOC_RESTART, 1),
new ExpectedEventType(ConnectionLossType.NONE, 1),
});
}
private void checkRetrieval(String retrievalPVName, ExpectedEventType[] expectedEvents) throws IOException {
RawDataRetrieval rawDataRetrieval = new RawDataRetrieval("http://localhost:" + ConfigServiceForTests.RETRIEVAL_TEST_PORT+ "/retrieval/data/getData.raw");
Timestamp now = TimeUtils.now();
Timestamp start = TimeUtils.minusDays(now, 366);
Timestamp end = now;
LinkedList<EpicsMessage> retrievedData = new LinkedList<EpicsMessage>();
try(GenMsgIterator strm = rawDataRetrieval.getDataForPV(retrievalPVName, start, end, false, null)) {
int eventCount = 0;
assertTrue("We should get some data, we are getting a null stream back", strm != null);
for(EpicsMessage dbrevent : strm) {
logger.info("Adding event with value " + dbrevent.getNumberValue().doubleValue()
+ " at time " + TimeUtils.convertToHumanReadableString(dbrevent.getTimestamp()));
retrievedData.add(dbrevent);
eventCount++;
}
assertTrue("Expecting at least one event. We got " + eventCount, eventCount >= 1);
}
int eventIndex = 0;
for(ExpectedEventType expectedEvent : expectedEvents) {
for(int i = 0; i < expectedEvent.numberOfEvents; i++) {
assertTrue("Ran out of events at " + eventIndex + " processed " + i + " expecting " + (expectedEvent.numberOfEvents-i) + "more", !retrievedData.isEmpty());
EpicsMessage message = retrievedData.poll();
assertTrue("Expecting event at " + eventIndex + " to be of type " + expectedEvent.lossType, expectedEvent.lossType == determineConnectionLossType(message));
eventIndex++;
}
}
}
private static ConnectionLossType determineConnectionLossType(EpicsMessage dbrevent) throws IOException {
ConnectionLossType retVal = ConnectionLossType.NONE;
HashMap<String, String> extraFields = dbrevent.getFieldValues();
if(!extraFields.keySet().contains("cnxlostepsecs")) {
retVal = ConnectionLossType.NONE;
} else {
String connectionLostSecs = extraFields.get("cnxlostepsecs");
if(Long.parseLong(connectionLostSecs) == 0) {
assertTrue("At least for now, we should have a startup field as well", extraFields.keySet().contains("startup"));
retVal = ConnectionLossType.STARTUP_OR_PAUSE_RESUME;
} else {
retVal = ConnectionLossType.IOC_RESTART;
}
}
logger.info("Event with value " + dbrevent.getNumberValue().doubleValue() + " is of type " + retVal);
return retVal;
}
}