/*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.server.measurement.test;
import static java.util.Arrays.asList;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import javax.inject.Inject;
import javax.persistence.Query;
import com.datastax.driver.core.exceptions.NoHostAvailableException;
import org.testng.annotations.Test;
import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.measurement.MeasurementBaseline;
import org.rhq.core.domain.measurement.MeasurementDefinition;
import org.rhq.core.domain.measurement.MeasurementOOB;
import org.rhq.core.domain.measurement.MeasurementSchedule;
import org.rhq.core.domain.measurement.NumericType;
import org.rhq.core.domain.measurement.composite.MeasurementOOBComposite;
import org.rhq.core.domain.resource.Agent;
import org.rhq.core.domain.resource.InventoryStatus;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.ResourceCategory;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.domain.util.PageControl;
import org.rhq.enterprise.server.measurement.MeasurementBaselineManagerLocal;
import org.rhq.enterprise.server.measurement.MeasurementOOBManagerLocal;
import org.rhq.enterprise.server.resource.ResourceManagerLocal;
import org.rhq.enterprise.server.storage.StorageClientManager;
import org.rhq.enterprise.server.test.AbstractEJB3Test;
import org.rhq.enterprise.server.util.LookupUtil;
import org.rhq.server.metrics.MetricsDAO;
import org.rhq.server.metrics.StorageSession;
import org.rhq.server.metrics.domain.AggregateNumericMetric;
import org.rhq.server.metrics.domain.Bucket;
import org.rhq.server.metrics.domain.MetricsTable;
public class MeasurementBaselineManagerTest extends AbstractEJB3Test {
private Agent agent;
private ResourceType platformType;
private Resource platform;
private Resource platform2;
private MeasurementDefinition measDef;
private MeasurementSchedule measSched;
private MeasurementSchedule measSched2;
private ResourceManagerLocal resourceManager;
private MeasurementBaselineManagerLocal baselineManager;
private MeasurementOOBManagerLocal oobManager;
private Subject overlord;
@Inject
private StorageClientManager storageClientManager;
private MetricsDAO metricsDAO;
// for the large inventory test
private List<Resource> allResources;
private List<MeasurementDefinition> allDefs;
private List<MeasurementSchedule> allScheds;
@Override
protected void beforeMethod() throws Exception {
this.resourceManager = LookupUtil.getResourceManager();
this.baselineManager = LookupUtil.getMeasurementBaselineManager();
this.oobManager = LookupUtil.getOOBManager();
this.overlord = LookupUtil.getSubjectManager().getOverlord();
metricsDAO = storageClientManager.getMetricsDAO();
this.prepareScheduler();
this.prepareForTestAgents();
}
@Override
protected void afterMethod() throws Exception {
this.unprepareForTestAgents();
this.unprepareScheduler();
}
/**
* Tests auto-calculation with a large inventory.
*
* @throws Throwable
*/
@Test
public void testAutoBaselineCalculationsWithLargeInventory() throws Throwable {
long startingTime;
begin();
try {
setupManyResources(20, 10);
long now = System.currentTimeMillis();
long eldest = now - 180000;
long elder = now - 120000;
long young = now - 60000;
long youngest = now;
int dataCount = allScheds.size();
for (MeasurementSchedule sched : allScheds) {
insertMeasurementDataNumeric1H(eldest, sched, 30.0, 20.0, 40.0);
insertMeasurementDataNumeric1H(elder, sched, 5.0, 2.0, 8.0);
insertMeasurementDataNumeric1H(young, sched, 6.0, 3.0, 9.0);
insertMeasurementDataNumeric1H(youngest, sched, 40.0, 30.0, 50.0);
if ((--dataCount % 500) == 0) {
System.out.println(String.valueOf(dataCount) + " more test measurement data left to insert");
commitAndBegin();
}
}
commit();
startingTime = System.currentTimeMillis();
long computeTime = baselineManager.calculateAutoBaselines(90000, System.currentTimeMillis());
assert computeTime > 0;
System.out.println(">>>>>>> a) [" + allScheds.size() + "] baselines calculated in ["
+ (System.currentTimeMillis() - startingTime) + "] ms");
// calculate them again, the delete query will be triggered this time
startingTime = System.currentTimeMillis();
computeTime = baselineManager.calculateAutoBaselines(90000, System.currentTimeMillis());
assert computeTime > 0;
System.out.println(">>>>>>> b) [" + allScheds.size() + "] baselines calculated in ["
+ (System.currentTimeMillis() - startingTime) + "] ms");
} catch (Throwable t) {
System.out.println("TEST FAILURE STACK TRACE FOLLOWS:");
t.printStackTrace();
throw t;
} finally {
try {
deleteManyResources();
getTransactionManager().rollback();
} catch (Exception e) {
}
allScheds.clear();
allDefs.clear();
allResources.clear();
allScheds = null;
allDefs = null;
allResources = null;
}
}
/**
* This is a very important test - it tests native queries that are hardcoded. We need this test because just
* deploying the EJB3 entities does not ensure these native queries didn't break; running this test will exercise
* the native queries.
*
* @throws Throwable
*/
@Test
public void testAutoBaselineCalculations() throws Throwable {
begin();
try {
setupResources();
assert em.find(Resource.class, platform.getId()) != null : "Did not setup platform - cannot test";
long now = System.currentTimeMillis();
long eldest = now - 180000;
long elder = now - 120000;
long young = now - 60000;
long youngest = now;
insertMeasurementDataNumeric1H(0, measSched, 0.0, 0.0, 0.0);
insertMeasurementDataNumeric1H(eldest, measSched, 30.0, 20.0, 40.0);
insertMeasurementDataNumeric1H(elder, measSched, 5.0, 2.0, 8.0);
insertMeasurementDataNumeric1H(young, measSched, 6.0, 3.0, 9.0);
insertMeasurementDataNumeric1H(youngest, measSched, 40.0, 30.0, 50.0);
insertMeasurementDataNumeric1H(0, measSched2, 0.0, 0.0, 0.0);
insertMeasurementDataNumeric1H(eldest, measSched2, 5000.0, 3500.0, 6500.0);
insertMeasurementDataNumeric1H(elder, measSched2, 5000.0, 3000.0, 7000.0);
insertMeasurementDataNumeric1H(young, measSched2, 2000.0, 1000.0, 3000.0);
insertMeasurementDataNumeric1H(youngest, measSched2, 1500.0, 500.0, 2500.0);
commit();
// pass now for olderThanTime to ensure all existing baselines are deleted
// pass 30000 for amountOfData to only include the youngest in the baseline calculation
long computeTime = baselineManager.calculateAutoBaselines(3000, System.currentTimeMillis());
assert computeTime > 0;
MeasurementBaseline bl1;
MeasurementBaseline bl2;
int bl1Id;
int bl2Id;
Date bl1ComputeTime;
Date bl2ComputeTime;
begin();
bl1 = em.find(MeasurementSchedule.class, measSched.getId()).getBaseline();
assert bl1 != null : "Baseline for measSched should have been inserted";
assert bl1.getSchedule().getId() == measSched.getId();
assert !bl1.isUserEntered();
assertEquals(30.0, bl1.getMin());
assertEquals(50.0, bl1.getMax());
assertEquals(40.0, bl1.getMean());
bl2 = em.find(MeasurementSchedule.class, measSched2.getId()).getBaseline();
assert bl2 != null : "Baseline for measSched2 should have been inserted";
assert bl2.getSchedule().getId() == measSched2.getId();
assert !bl2.isUserEntered();
assertEquals(500.0, bl2.getMin());
assertEquals(2500.0, bl2.getMax());
assertEquals(1500.0, bl2.getMean());
// remember these, the next time we calculate, they will be deleted and new ones created
bl1Id = bl1.getId();
bl2Id = bl2.getId();
bl1ComputeTime = bl1.getComputeTime();
bl2ComputeTime = bl2.getComputeTime();
commit();
// calculate them again, the values will be the same, but the delete query will be triggered
// wait a bit so our compute time will be assured to be different
Thread.sleep(1000L);
computeTime = baselineManager.calculateAutoBaselines(30000, System.currentTimeMillis());
assert computeTime > 0;
begin();
bl1 = em.find(MeasurementSchedule.class, measSched.getId()).getBaseline();
assert bl1 != null : "Baseline for measSched should have been inserted";
assert bl1.getSchedule().getId() == measSched.getId();
assert !bl1.isUserEntered();
assertEquals(30.0, bl1.getMin());
assertEquals(50.0, bl1.getMax());
assertEquals(40.0, bl1.getMean());
bl2 = em.find(MeasurementSchedule.class, measSched2.getId()).getBaseline();
assert bl2 != null : "Baseline for measSched2 should have been inserted";
assert bl2.getSchedule().getId() == measSched2.getId();
assert !bl2.isUserEntered();
assertEquals(500.0, bl2.getMin());
assertEquals(2500.0, bl2.getMax());
assertEquals(1500.0, bl2.getMean());
// check the new IDs with the old ones - they should be different due to the delete query
assert bl1.getId() != bl1Id : "bl1.getId() was " + bl1.getId() + ", bl1Id was " + bl1Id;
assert bl2.getId() != bl2Id : "bl2.getId() was " + bl2.getId() + ", bl2Id was " + bl2Id;
assert bl1.getComputeTime().after(bl1ComputeTime);
assert bl2.getComputeTime().after(bl2ComputeTime);
commit();
// calculate them again using wider ranges to include more (all available) 1H data in the calculation
Thread.sleep(1000L);
computeTime = baselineManager.calculateAutoBaselines(240000, System.currentTimeMillis());
assert computeTime > 0;
begin();
bl1 = em.find(MeasurementSchedule.class, measSched.getId()).getBaseline();
assert bl1 != null : "Baseline for measSched should have been inserted";
assert bl1.getSchedule().getId() == measSched.getId();
assert !bl1.isUserEntered();
assertEquals(2.0, bl1.getMin());
assertEquals(50.00, bl1.getMax());
assertEquals(20.25, bl1.getMean());
bl2 = em.find(MeasurementSchedule.class, measSched2.getId()).getBaseline();
assert bl2 != null : "Baseline for measSched2 should have been inserted";
assert bl2.getSchedule().getId() == measSched2.getId();
assert !bl2.isUserEntered();
assertEquals(500.0, bl2.getMin());
assertEquals(7000.0, bl2.getMax());
assertEquals(3375.0, bl2.getMean());
// check the new IDs with the old ones - they should be different due to the delete query
assert bl1.getId() != bl1Id;
assert bl2.getId() != bl2Id;
assert bl1.getComputeTime().after(bl1ComputeTime);
assert bl2.getComputeTime().after(bl2ComputeTime);
commit();
} catch (Throwable t) {
System.out.println("TEST FAILURE STACK TRACE FOLLOWS:");
t.printStackTrace();
throw t;
} finally {
try {
deleteResources();
getTransactionManager().rollback();
} catch (Exception e) {
}
}
}
/**
* This test is disabled as the implementation of MeasurementOOBManagerBean is
* getting refactored. MeasurementOOBManagerBeanTest has been added to exercise the new
* implementation.
*/
@SuppressWarnings("unchecked")
@Test(enabled = false)
public void testCalculateOOB() throws Throwable {
begin();
try {
setupResources();
assert em.find(Resource.class, platform.getId()) != null : "Did not setup platform - cannot test";
long now = System.currentTimeMillis();
long eldest = now - 180000;
long elder = now - 120000;
long young = now - 60000;
long youngest = now;
insertMeasurementDataNumeric1H(0, measSched, 0.0, 0.0, 0.0);
insertMeasurementDataNumeric1H(eldest, measSched, 30.0, 20.0, 40.0);
insertMeasurementDataNumeric1H(elder, measSched, 5.0, 2.0, 8.0);
insertMeasurementDataNumeric1H(young, measSched, 6.0, 3.0, 9.0);
insertMeasurementDataNumeric1H(youngest, measSched, 40.0, 30.0, 50.0);
insertMeasurementDataNumeric1H(0, measSched2, 0.0, 0.0, 0.0);
insertMeasurementDataNumeric1H(eldest, measSched2, 5000.0, 3500.0, 6500.0);
insertMeasurementDataNumeric1H(elder, measSched2, 5000.0, 3000.0, 7000.0);
insertMeasurementDataNumeric1H(young, measSched2, 2000.0, 1000.0, 3000.0);
insertMeasurementDataNumeric1H(youngest, measSched2, 1500.0, 500.0, 2500.0);
List<AggregateNumericMetric> aggregates = asList(
new AggregateNumericMetric(measSched.getId(), Bucket.ONE_HOUR, 0.0, 0.0, 0.0, 0),
new AggregateNumericMetric(measSched.getId(), Bucket.ONE_HOUR, 30.0, 20.0, 40.0, eldest),
new AggregateNumericMetric(measSched.getId(), Bucket.ONE_HOUR, 5.0, 2.0, 8.0, elder),
new AggregateNumericMetric(measSched.getId(), Bucket.ONE_HOUR, 6.0, 3.0, 9.0, young),
new AggregateNumericMetric(measSched.getId(), Bucket.ONE_HOUR, 40.0, 30.0, 50.0, youngest),
new AggregateNumericMetric(measSched2.getId(), Bucket.ONE_HOUR, 40.0, 0.0, 0.0, 0),
new AggregateNumericMetric(measSched2.getId(), Bucket.ONE_HOUR, 5000.0, 3500.0, 6500.0, eldest),
new AggregateNumericMetric(measSched2.getId(), Bucket.ONE_HOUR, 5000.0, 3000.0, 7000.0, elder),
new AggregateNumericMetric(measSched2.getId(), Bucket.ONE_HOUR, 2000.0, 1000.0, 3000.0, young),
new AggregateNumericMetric(measSched2.getId(), Bucket.ONE_HOUR, 1500.0, 500.0, 2500.0, youngest)
);
commit();
long computeTime = baselineManager.calculateAutoBaselines(30000, System.currentTimeMillis());
assert computeTime > 0;
begin();
// check the 2 values at 5000 against their bands computed between [500 and 1000]
oobManager.computeOOBsForLastHour(overlord, aggregates);
// check results
Query q = em.createQuery("SELECT oo FROM MeasurementOOB oo");
List<MeasurementOOB> oobs = q.getResultList();
System.out.println("OOBs calculated: \n" + oobs);
for (MeasurementOOB oob : oobs) {
if (oob.getScheduleId() == measSched.getId()) {
assert oob.getOobFactor() == 50 : "Expected: 50, was " + oob.getOobFactor();
} else {
assert oob.getOobFactor() == 200 : "Expected: 200, was " + oob.getOobFactor();
}
}
PageControl pc = PageControl.getUnlimitedInstance();
List<MeasurementOOBComposite> comps = oobManager.getSchedulesWithOOBs(overlord, null, null, null, pc);
// System.out.println("Composites: " + comps);
assert comps.size() == 2 : "Expected 2 composites, but got " + comps.size();
comps = oobManager.getHighestNOOBsForResource(overlord, platform.getId(), 2);
assert comps.size() == 1 : "Expected 1 composite, but got " + comps.size();
// Compute some more OOBs
oobManager.computeOOBsFromHourBeginingAt(overlord, elder);
q = em.createQuery("SELECT oo FROM MeasurementOOB oo");
oobs = q.getResultList();
// System.out.println("OOBs calculated: \n" + oobs);
comps = oobManager.getSchedulesWithOOBs(overlord, null, null, null, pc);
// System.out.println("Composites: " + comps);
assert comps.size() == 2 : "Expected 2, but was " + comps.size();
commit();
// Clean up
begin();
q = em.createQuery("DELETE FROM MeasurementOOB oo WHERE oo.id = :sched1 OR oo.id = :sched2");
q.setParameter("sched1", measSched.getId());
q.setParameter("sched2", measSched2.getId());
q.executeUpdate();
commit();
} catch (Throwable t) {
System.out.println("TEST FAILURE STACK TRACE FOLLOWS:");
t.printStackTrace();
throw t;
} finally {
try {
deleteResources();
getTransactionManager().rollback();
} catch (Exception e) {
}
}
}
private void setupResources() {
agent = new Agent("test-agent", "localhost", 1234, "", "randomToken");
em.persist(agent);
platformType = new ResourceType("testplatAB", "p", ResourceCategory.PLATFORM, null);
em.persist(platformType);
platform = new Resource("platform1", "testAutoBaseline Platform One", platformType);
platform.setUuid("" + new Random().nextInt());
platform.setInventoryStatus(InventoryStatus.COMMITTED);
em.persist(platform);
platform.setAgent(agent);
platform2 = new Resource("platform2", "testAutoBaseline Platform Two", platformType);
platform2.setUuid("" + new Random().nextInt());
platform2.setInventoryStatus(InventoryStatus.COMMITTED);
// deleteResource removes the agent, so we can't have two direct platforms for it, make one a child of the other
platform.addChildResource(platform2);
em.persist(platform2);
platform2.setAgent(agent);
measDef = new MeasurementDefinition(platformType, "testAutoBaseline");
measDef.setDefaultOn(true);
measDef.setDisplayName("testAutoBaseline Measurement Display Name");
measDef.setMeasurementType(NumericType.DYNAMIC);
em.persist(measDef);
measSched = new MeasurementSchedule(measDef, platform);
measSched.setEnabled(true);
platform.addSchedule(measSched);
measDef.addSchedule(measSched);
em.persist(measSched);
measSched2 = new MeasurementSchedule(measDef, platform2);
measSched2.setEnabled(true);
platform2.addSchedule(measSched2);
measDef.addSchedule(measSched2);
em.persist(measSched2);
}
private void deleteResources() {
Object doomed;
try {
// perform in-band and out-of-band work in quick succession
// deleteResource will remove platform and platform2, as well as the agent
List<Integer> deletedIds = resourceManager.uninventoryResource(overlord, platform.getId());
for (Integer deletedResourceId : deletedIds) {
resourceManager.uninventoryResourceAsyncWork(overlord, deletedResourceId);
}
begin();
deleteMeasurementDataNumeric1H(measSched);
deleteMeasurementDataNumeric1H(measSched2);
if ((doomed = em.find(MeasurementSchedule.class, measSched.getId())) != null) {
em.remove(doomed);
}
if ((doomed = em.find(MeasurementSchedule.class, measSched2.getId())) != null) {
em.remove(doomed);
}
if ((doomed = em.find(MeasurementDefinition.class, measDef.getId())) != null) {
em.remove(doomed);
}
if ((doomed = em.find(ResourceType.class, platformType.getId())) != null) {
em.remove(doomed);
}
commit();
} catch (Exception e) {
System.out.println("Cannot delete test resources, database still has test data in it");
e.printStackTrace();
}
}
private void setupManyResources(int resourceCount, int measurementCount) {
allResources = new ArrayList<Resource>(resourceCount);
allDefs = new ArrayList<MeasurementDefinition>(measurementCount);
allScheds = new ArrayList<MeasurementSchedule>(resourceCount * measurementCount);
platformType = new ResourceType("testplatAB", "p", ResourceCategory.PLATFORM, null);
em.persist(platformType);
for (int m = 0; m < measurementCount; m++) {
MeasurementDefinition def = new MeasurementDefinition(platformType, "testAutoBaselineMeasDef" + m);
def.setDefaultOn(true);
def.setDisplayName("testAutoBaseline Measurement Display Name" + m);
def.setMeasurementType(NumericType.DYNAMIC);
em.persist(def);
allDefs.add(def);
}
em.flush();
em.clear();
System.out.println("Populating test inventory with [" + resourceCount + "] resources.");
agent = new Agent("test-agent", "localhost", 1234, "", "randomToken");
em.persist(agent);
Resource root = null;
for (int r = 0; r < resourceCount; r++) {
Resource resource = new Resource(String.valueOf(r), "testAutoBaselineResource" + r, platformType);
resource.setUuid("" + new Random().nextInt());
if (root == null) {
root = resource;
} else {
root.addChildResource(resource);
}
em.persist(resource);
allResources.add(resource);
resource.setAgent(agent);
for (MeasurementDefinition def : allDefs) {
MeasurementSchedule sched = new MeasurementSchedule(def, resource);
sched.setEnabled(true);
resource.addSchedule(sched);
def.addSchedule(sched);
em.persist(sched);
allScheds.add(sched);
}
if ((r % 50) == 0) {
System.out.println("..." + r);
em.flush();
em.clear();
}
}
em.flush();
em.clear();
System.out.println("Test inventory now has [" + resourceCount + "] resources.");
return;
}
private void deleteManyResources() {
Object doomed;
try {
int dataCount = allScheds.size();
begin();
for (MeasurementSchedule doomedSched : allScheds) {
deleteMeasurementDataNumeric1H(doomedSched);
if ((--dataCount % 1000) == 0) {
System.out.println(String.valueOf(dataCount) + " more test measurement data left to delete");
commitAndBegin();
}
}
commit();
// delete the resources which will cascade delete all schedules
Resource root = allResources.get(0);
// perform in-band and out-of-band work in quick succession
// this also deletes the agent
List<Integer> deletedIds = resourceManager.uninventoryResource(overlord, root.getId());
for (Integer deletedResourceId : deletedIds) {
resourceManager.uninventoryResourceAsyncWork(overlord, deletedResourceId);
}
begin();
for (MeasurementDefinition doomedDef : allDefs) {
if ((doomed = em.find(MeasurementDefinition.class, doomedDef.getId())) != null) {
em.remove(doomed);
}
}
if ((doomed = em.find(ResourceType.class, platformType.getId())) != null) {
em.remove(doomed);
}
commit();
} catch (Exception e) {
System.out.println("Cannot delete test resources, database still has test data in it");
e.printStackTrace();
}
}
// commitAndBegin, commit, begin are used mainly when testing in a debugger
// so you can commit the data, and examine it in an external SQL tool without
// worrying about transactions timing out within the paused test thread.
private void commitAndBegin() throws Exception {
commit();
begin(); // put a breakpoint here and call commitAndBegin at a place where you want to pause the test
}
private void commit() throws Exception {
em.flush();
getTransactionManager().commit();
}
private void begin() throws Exception {
getTransactionManager().begin();
}
private void insertMeasurementDataNumeric1H(long timeStamp, MeasurementSchedule schedule, double value, double min,
double max) {
AggregateNumericMetric metric = new AggregateNumericMetric(schedule.getId(), Bucket.ONE_HOUR, value, min, max,
timeStamp);
metricsDAO.insert1HourData(metric);
}
private void deleteMeasurementDataNumeric1H(MeasurementSchedule schedule) {
try {
StorageSession session = storageClientManager.getSession();
session.execute("DELETE FROM " + MetricsTable.AGGREGATE.getTableName() + " WHERE schedule_id = " +
schedule.getId() + " AND bucket = 'one_hour'");
} catch (NoHostAvailableException e) {
throw new RuntimeException("An error occurred while trying to deleted data from "
+ MetricsTable.AGGREGATE.getTableName() + " for " + schedule, e);
}
}
}