Repository: ambari Updated Branches: refs/heads/trunk 1eba77a93 -> 580e920a1
AMBARI-18271 - Multiple Aggregate Alerts For JournalNode Exist With Some Being Incorrect (jonathanhurley) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/580e920a Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/580e920a Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/580e920a Branch: refs/heads/trunk Commit: 580e920a145cebf0e3899bc8ea4e3fc4b4d367dc Parents: 1eba77a Author: Jonathan Hurley <[email protected]> Authored: Mon Aug 29 12:30:51 2016 -0400 Committer: Jonathan Hurley <[email protected]> Committed: Mon Aug 29 23:35:47 2016 -0400 ---------------------------------------------------------------------- .../listeners/alerts/AlertReceivedListener.java | 109 +++++++++++++------ .../server/orm/entities/AlertCurrentEntity.java | 36 ++++-- .../orm/entities/AlertDefinitionEntity.java | 37 +++++-- .../server/orm/entities/AlertHistoryEntity.java | 59 ++++++++-- .../orm/entities/AlertCurrentEntityTest.java | 64 +++++++++++ .../orm/entities/AlertDefinitionEntityTest.java | 69 ++++++++++++ .../orm/entities/AlertHistoryEntityTest.java | 89 +++++++++++++++ .../state/alerts/AlertReceivedListenerTest.java | 45 ++++++++ 8 files changed, 447 insertions(+), 61 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/580e920a/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertReceivedListener.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertReceivedListener.java b/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertReceivedListener.java index 2dcf1d6..907e4d8 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertReceivedListener.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/events/listeners/alerts/AlertReceivedListener.java @@ -19,6 +19,8 @@ package org.apache.ambari.server.events.listeners.alerts; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.concurrent.locks.Lock; import org.apache.ambari.server.AmbariException; import org.apache.ambari.server.EagerSingleton; @@ -52,6 +54,7 @@ import org.slf4j.LoggerFactory; import com.google.common.eventbus.AllowConcurrentEvents; import com.google.common.eventbus.Subscribe; +import com.google.common.util.concurrent.Striped; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; @@ -101,6 +104,12 @@ public class AlertReceivedListener { private AlertEventPublisher m_alertEventPublisher; /** + * Used for ensuring that creation of {@link AlertCurrentEntity} instances has fine-grain + * locks to prevent duplicates. + */ + private Striped<Lock> creationLocks = Striped.lazyWeakLock(100); + + /** * Constructor. * * @param publisher @@ -129,7 +138,7 @@ public class AlertReceivedListener { // many transactions/commits List<Alert> alerts = event.getAlerts(); - List<AlertCurrentEntity> toCreate = new ArrayList<>(); + // these can be wrapped in their own transaction List<AlertCurrentEntity> toMerge = new ArrayList<>(); List<AlertCurrentEntity> toCreateHistoryAndMerge = new ArrayList<>(); @@ -173,47 +182,63 @@ public class AlertReceivedListener { AlertCurrentEntity current; AlertState alertState = alert.getState(); - if (StringUtils.isBlank(alert.getHostName()) || definition.isHostIgnored()) { - current = m_alertsDao.findCurrentByNameNoHost(clusterId, alert.getName()); - } else { - current = m_alertsDao.findCurrentByHostAndName(clusterId, alert.getHostName(), - alert.getName()); - } + // attempt to lookup the current alert + current = getCurrentEntity(clusterId, alert, definition); + + // if it doesn't exist then we must create it, ensuring that two or more + // aren't created from other threads + if( null == current ){ - if (null == current) { - // if there is no current alert and the state is skipped, then simple + // if there is no current alert and the state is skipped, then simply // skip over this one as there is nothing to update in the databse if (alertState == AlertState.SKIPPED) { continue; } - AlertHistoryEntity history = createHistory(clusterId, definition, alert); + // create a key out of the cluster/definition name/host (possibly null) + int key = Objects.hash(clusterId, alert.getName(), alert.getHostName()); + Lock lock = creationLocks.get(key); + lock.lock(); - // this new alert must reflect the correct MM state for the - // service/component/host - MaintenanceState maintenanceState = MaintenanceState.OFF; + // attempt to lookup the current alert again to ensure that a previous + // thread didn't already create it try { - maintenanceState = m_maintenanceStateHelper.get().getEffectiveState(clusterId, alert); - } catch (Exception exception) { - LOG.error("Unable to determine the maintenance mode state for {}, defaulting to OFF", - alert, exception); - } + // if it's not null anymore, then there's no work to do here + current = getCurrentEntity(clusterId, alert, definition); + if( null != current ) { + continue; + } - current = new AlertCurrentEntity(); - current.setMaintenanceState(maintenanceState); - current.setAlertHistory(history); - current.setLatestTimestamp(alert.getTimestamp()); - current.setOriginalTimestamp(alert.getTimestamp()); + // the current alert is still null, so go through and create it + AlertHistoryEntity history = createHistory(clusterId, definition, alert); + + // this new alert must reflect the correct MM state for the + // service/component/host + MaintenanceState maintenanceState = MaintenanceState.OFF; + try { + maintenanceState = m_maintenanceStateHelper.get().getEffectiveState(clusterId, alert); + } catch (Exception exception) { + LOG.error("Unable to determine the maintenance mode state for {}, defaulting to OFF", + alert, exception); + } - // brand new alert instances being received are always HARD - current.setFirmness(AlertFirmness.HARD); + current = new AlertCurrentEntity(); + current.setMaintenanceState(maintenanceState); + current.setAlertHistory(history); + current.setLatestTimestamp(alert.getTimestamp()); + current.setOriginalTimestamp(alert.getTimestamp()); - // store the entity for creation later - toCreate.add(current); + // brand new alert instances being received are always HARD + current.setFirmness(AlertFirmness.HARD); - // create the event to fire later - alertEvents.add(new InitialAlertEvent(clusterId, alert, current)); + m_alertsDao.create(current); + // create the event to fire later + alertEvents.add(new InitialAlertEvent(clusterId, alert, current)); + } finally { + // release the lock for this alert + lock.unlock(); + } } else if (alertState == current.getAlertHistory().getAlertState() || alertState == AlertState.SKIPPED) { @@ -319,7 +344,7 @@ public class AlertReceivedListener { // invokes the EntityManager create/merge on various entities in a single // transaction - saveEntities(toCreate, toMerge, toCreateHistoryAndMerge); + saveEntities(toMerge, toCreateHistoryAndMerge); // broadcast events for (AlertEvent eventToFire : alertEvents) { @@ -343,18 +368,30 @@ public class AlertReceivedListener { } /** + * Gets the {@link AlertCurrentEntity} which cooresponds to the new alert being received, if any. + * + * @param clusterId the ID of the cluster. + * @param alert the alert being received (not {@code null}). + * @param definition the {@link AlertDefinitionEntity} for the alert being received (not {@code null}). + * @return the existing current alert or {@code null} for none. + */ + private AlertCurrentEntity getCurrentEntity(long clusterId, Alert alert, AlertDefinitionEntity definition){ + if (StringUtils.isBlank(alert.getHostName()) || definition.isHostIgnored()) { + return m_alertsDao.findCurrentByNameNoHost(clusterId, alert.getName()); + } else { + return m_alertsDao.findCurrentByHostAndName(clusterId, alert.getHostName(), + alert.getName()); + } + } + + /** * Saves alert and alert history entities in single transaction - * @param toCreate - new alerts, create alert and history * @param toMerge - merge alert only * @param toCreateHistoryAndMerge - create new history, merge alert */ @Transactional - void saveEntities(List<AlertCurrentEntity> toCreate, List<AlertCurrentEntity> toMerge, + void saveEntities(List<AlertCurrentEntity> toMerge, List<AlertCurrentEntity> toCreateHistoryAndMerge) { - for (AlertCurrentEntity entity : toCreate) { - m_alertsDao.create(entity); - } - for (AlertCurrentEntity entity : toMerge) { m_alertsDao.merge(entity, m_configuration.isAlertCacheEnabled()); } http://git-wip-us.apache.org/repos/asf/ambari/blob/580e920a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertCurrentEntity.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertCurrentEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertCurrentEntity.java index b30b100..651e1a1 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertCurrentEntity.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertCurrentEntity.java @@ -17,6 +17,8 @@ */ package org.apache.ambari.server.orm.entities; +import java.util.Objects; + import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; @@ -36,6 +38,7 @@ import javax.persistence.TableGenerator; import org.apache.ambari.server.state.AlertFirmness; import org.apache.ambari.server.state.AlertState; import org.apache.ambari.server.state.MaintenanceState; +import org.apache.commons.lang.builder.EqualsBuilder; /** * The {@link AlertCurrentEntity} class represents the most recently received an @@ -331,7 +334,13 @@ public class AlertCurrentEntity { } /** - * + * Gets the equality to another alert based on the following criteria: + * <ul> + * <li>{@link #alertId} + * <li>{@link #alertHistory} + * </ul> + * <p/> + * {@inheritDoc} */ @Override public boolean equals(Object object) { @@ -344,21 +353,30 @@ public class AlertCurrentEntity { } AlertCurrentEntity that = (AlertCurrentEntity) object; + EqualsBuilder equalsBuilder = new EqualsBuilder(); - if (alertId != null ? !alertId.equals(that.alertId) : that.alertId != null) { - return false; - } - - return true; + equalsBuilder.append(alertId, that.alertId); + equalsBuilder.append(alertHistory, that.alertHistory); + return equalsBuilder.isEquals(); } /** - * + * Generates a hash for the current alert based on the following criteria: + * <ul> + * <li>{@link #alertId} + * <li>{@link #alertHistory} + * </ul> + * <p/> + * For new alerts, the associated {@link AlertHistoryEntity} may not be + * persisted yet. This will rely on the + * {@link AlertHistoryEntity#equals(Object)} and + * {@link AlertHistoryEntity#hashCode()} being correct. + * <p/> + * {@inheritDoc} */ @Override public int hashCode() { - int result = null != alertId ? alertId.hashCode() : 0; - return result; + return Objects.hash(alertId, alertHistory); } /** http://git-wip-us.apache.org/repos/asf/ambari/blob/580e920a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertDefinitionEntity.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertDefinitionEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertDefinitionEntity.java index 638646c..0eebf4b 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertDefinitionEntity.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertDefinitionEntity.java @@ -20,6 +20,7 @@ package org.apache.ambari.server.orm.entities; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; +import java.util.Objects; import java.util.Set; import javax.persistence.Basic; @@ -593,7 +594,17 @@ public class AlertDefinitionEntity { } /** - * + * Gets the equality to another historical alert entry based on the following criteria: + * <ul> + * <li>{@link #definitionId} + * <li>{@link #clusterId} + * <li>{@link #definitionName} + * </ul> + * <p/> + * However, since we're guaranteed that {@link #definitionId} is unique among persisted entities, we + * can return the hashCode based on this value if it is set. + * <p/> + * {@inheritDoc} */ @Override public boolean equals(Object object) { @@ -607,21 +618,31 @@ public class AlertDefinitionEntity { AlertDefinitionEntity that = (AlertDefinitionEntity) object; - if (definitionId != null ? !definitionId.equals(that.definitionId) - : that.definitionId != null) { - return false; + // use the unique ID if it exists + if( null != definitionId ){ + return Objects.equals(definitionId, that.definitionId); } - return true; + return Objects.equals(definitionId, that.definitionId) && + Objects.equals(clusterId, that.clusterId) && + Objects.equals(definitionName, that.definitionName); } /** - * + * Gets a hash to uniquely identify this alert definition. Since we're + * guaranteed that {@link #definitionId} is unique among persisted entities, + * we can return the hashCode based on this value if it is set. + * <p/> + * {@inheritDoc} */ @Override public int hashCode() { - int result = null != definitionId ? definitionId.hashCode() : 0; - return result; + // use the unique ID if it exists + if( null != definitionId ){ + return definitionId.hashCode(); + } + + return Objects.hash(definitionId, clusterId, definitionName); } /** http://git-wip-us.apache.org/repos/asf/ambari/blob/580e920a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertHistoryEntity.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertHistoryEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertHistoryEntity.java index 8bc4b99..958740a 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertHistoryEntity.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/AlertHistoryEntity.java @@ -17,6 +17,8 @@ */ package org.apache.ambari.server.orm.entities; +import java.util.Objects; + import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; @@ -331,7 +333,7 @@ public class AlertHistoryEntity { */ public void setAlertDefinition(AlertDefinitionEntity alertDefinition) { this.alertDefinition = alertDefinition; - this.alertDefinitionId = alertDefinition.getDefinitionId(); + alertDefinitionId = alertDefinition.getDefinitionId(); } /** @@ -351,7 +353,25 @@ public class AlertHistoryEntity { } /** - * + * Gets the equality to another historical alert entry based on the following criteria: + * <ul> + * <li>{@link #alertId} + * <li>{@link #clusterId} + * <li>{@link #alertInstance} + * <li>{@link #alertLabel} + * <li>{@link #alertState} + * <li>{@link #alertText} + * <li>{@link #alertTimestamp} + * <li>{@link #serviceName} + * <li>{@link #componentName} + * <li>{@link #hostName} + * <li>{@link #alertDefinition} + * </ul> + * <p/> + * However, since we're guaranteed that {@link #alertId} is unique among persisted entities, we + * can return the hashCode based on this value if it is set. + * <p/> + * {@inheritDoc} */ @Override public boolean equals(Object object) { @@ -365,20 +385,43 @@ public class AlertHistoryEntity { AlertHistoryEntity that = (AlertHistoryEntity) object; - if (alertId != null ? !alertId.equals(that.alertId) : that.alertId != null) { - return false; + // use the unique ID if it exists + if( null != alertId ){ + return Objects.equals(alertId, that.alertId); } - return true; + return Objects.equals(alertId, that.alertId) && + Objects.equals(clusterId, that.clusterId) && + Objects.equals(alertInstance, that.alertInstance) && + Objects.equals(alertLabel, that.alertLabel) && + Objects.equals(alertState, that.alertState) && + Objects.equals(alertText, that.alertText) && + Objects.equals(alertTimestamp, that.alertTimestamp) && + Objects.equals(serviceName, that.serviceName) && + Objects.equals(componentName, that.componentName) && + Objects.equals(hostName, that.hostName) && + Objects.equals(alertDefinition, that.alertDefinition); } /** - * + * Gets a hash to uniquely identify this historical alert instance. Since historical entries + * have no real uniqueness properties, we need to rely on a combination of the fields of this + * entity. + * <p/> + * However, since we're guaranteed that {@link #alertId} is unique among persisted entities, we + * can return the hashCode based on this value if it is set. + * <p/> + * {@inheritDoc} */ @Override public int hashCode() { - int result = null != alertId ? alertId.hashCode() : 0; - return result; + // use the unique ID if it exists + if( null != alertId ){ + return alertId.hashCode(); + } + + return Objects.hash(alertId, clusterId, alertInstance, alertLabel, alertState, alertText, + alertTimestamp, serviceName, componentName, hostName, alertDefinition); } /** http://git-wip-us.apache.org/repos/asf/ambari/blob/580e920a/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/AlertCurrentEntityTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/AlertCurrentEntityTest.java b/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/AlertCurrentEntityTest.java new file mode 100644 index 0000000..73d2c7e --- /dev/null +++ b/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/AlertCurrentEntityTest.java @@ -0,0 +1,64 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ambari.server.orm.entities; + +import java.util.Objects; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests methods on {@link AlertCurrentEntity}. + */ +public class AlertCurrentEntityTest { + + /** + * Tests {@link AlertCurrentEntity#hashCode()} and {@link AlertCurrentEntity#equals(Object)} + */ + @Test + public void testHashCodeAndEquals(){ + AlertHistoryEntity history1 = new AlertHistoryEntity(); + AlertHistoryEntity history2 = new AlertHistoryEntity(); + history1.setAlertId(1L); + history2.setAlertId(2L); + + AlertCurrentEntity current1 = new AlertCurrentEntity(); + AlertCurrentEntity current2 = new AlertCurrentEntity(); + + Assert.assertEquals(current1.hashCode(), current2.hashCode()); + Assert.assertTrue(Objects.equals(current1, current2)); + + current1.setAlertHistory(history1); + current2.setAlertHistory(history2); + Assert.assertNotSame(current1.hashCode(), current2.hashCode()); + Assert.assertFalse(Objects.equals(current1, current2)); + + current2.setAlertHistory(history1); + Assert.assertEquals(current1.hashCode(), current2.hashCode()); + Assert.assertTrue(Objects.equals(current1, current2)); + + current1.setAlertId(1L); + current2.setAlertId(2L); + Assert.assertNotSame(current1.hashCode(), current2.hashCode()); + Assert.assertFalse(Objects.equals(current1, current2)); + + current2.setAlertId(1L); + Assert.assertEquals(current1.hashCode(), current2.hashCode()); + Assert.assertTrue(Objects.equals(current1, current2)); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/580e920a/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/AlertDefinitionEntityTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/AlertDefinitionEntityTest.java b/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/AlertDefinitionEntityTest.java new file mode 100644 index 0000000..fc4bc90 --- /dev/null +++ b/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/AlertDefinitionEntityTest.java @@ -0,0 +1,69 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ambari.server.orm.entities; + +import java.util.Objects; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests methods on {@link AlertDefinitionEntity}. + */ +public class AlertDefinitionEntityTest { + + /** + * Tests {@link AlertDefinitionEntity#hashCode()} and {@link AlertDefinitionEntity#equals(Object)} + */ + @Test + public void testHashCodeAndEquals(){ + AlertDefinitionEntity definition1 = new AlertDefinitionEntity(); + AlertDefinitionEntity definition2 = new AlertDefinitionEntity(); + + Assert.assertEquals(definition1.hashCode(), definition2.hashCode()); + Assert.assertTrue(Objects.equals(definition1, definition2)); + + definition1.setClusterId(1L); + definition2.setClusterId(1L); + Assert.assertEquals(definition1.hashCode(), definition2.hashCode()); + Assert.assertTrue(Objects.equals(definition1, definition2)); + + definition1.setDefinitionName("definition-name"); + definition2.setDefinitionName("definition-name"); + Assert.assertEquals(definition1.hashCode(), definition2.hashCode()); + Assert.assertTrue(Objects.equals(definition1, definition2)); + + definition2.setDefinitionName("definition-name-foo"); + Assert.assertNotSame(definition1.hashCode(), definition2.hashCode()); + Assert.assertFalse(Objects.equals(definition1, definition2)); + + definition2.setDefinitionName("definition-name"); + definition2.setClusterId(2L); + Assert.assertNotSame(definition1.hashCode(), definition2.hashCode()); + Assert.assertFalse(Objects.equals(definition1, definition2)); + + definition2.setClusterId(1L); + definition1.setDefinitionId(1L); + Assert.assertNotSame(definition1.hashCode(), definition2.hashCode()); + Assert.assertFalse(Objects.equals(definition1, definition2)); + + definition2.setDefinitionId(1L); + Assert.assertEquals(definition1.hashCode(), definition2.hashCode()); + Assert.assertTrue(Objects.equals(definition1, definition2)); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/580e920a/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/AlertHistoryEntityTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/AlertHistoryEntityTest.java b/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/AlertHistoryEntityTest.java new file mode 100644 index 0000000..beac248 --- /dev/null +++ b/ambari-server/src/test/java/org/apache/ambari/server/orm/entities/AlertHistoryEntityTest.java @@ -0,0 +1,89 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ambari.server.orm.entities; + +import java.util.Objects; + +import org.apache.ambari.server.state.AlertState; +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests methods on {@link AlertHistoryEntity}. + */ +public class AlertHistoryEntityTest { + + /** + * Tests {@link AlertHistoryEntity#hashCode()} and {@link AlertHistoryEntity#equals(Object)} + */ + @Test + public void testHashCodeAndEquals(){ + AlertDefinitionEntity definition1 = new AlertDefinitionEntity(); + definition1.setDefinitionId(1L); + AlertDefinitionEntity definition2 = new AlertDefinitionEntity(); + definition2.setDefinitionId(2L); + + AlertHistoryEntity history1 = new AlertHistoryEntity(); + AlertHistoryEntity history2 = new AlertHistoryEntity(); + + Assert.assertEquals(history1.hashCode(), history2.hashCode()); + Assert.assertTrue(Objects.equals(history1, history2)); + + history1.setAlertDefinition(definition1); + history2.setAlertDefinition(definition2); + Assert.assertNotSame(history1.hashCode(), history2.hashCode()); + Assert.assertFalse(Objects.equals(history1, history2)); + + history2.setAlertDefinition(definition1); + Assert.assertEquals(history1.hashCode(), history2.hashCode()); + Assert.assertTrue(Objects.equals(history1, history2)); + + history1.setAlertLabel("label"); + history1.setAlertState(AlertState.OK); + history1.setAlertText("text"); + history1.setAlertTimestamp(1L); + history1.setClusterId(1L); + history1.setComponentName("component"); + history1.setServiceName("service"); + history1.setHostName("host"); + history2.setAlertLabel("label"); + history2.setAlertState(AlertState.OK); + history2.setAlertText("text"); + history2.setAlertTimestamp(1L); + history2.setClusterId(1L); + history2.setComponentName("component"); + history2.setServiceName("service"); + history2.setHostName("host"); + Assert.assertEquals(history1.hashCode(), history2.hashCode()); + Assert.assertTrue(Objects.equals(history1, history2)); + + history2.setAlertState(AlertState.CRITICAL); + Assert.assertNotSame(history1.hashCode(), history2.hashCode()); + Assert.assertFalse(Objects.equals(history1, history2)); + + history2.setAlertState(AlertState.OK); + history1.setAlertId(1L); + history2.setAlertId(1L); + Assert.assertEquals(history1.hashCode(), history2.hashCode()); + Assert.assertTrue(Objects.equals(history1, history2)); + + history2.setAlertId(2L); + Assert.assertNotSame(history1.hashCode(), history2.hashCode()); + Assert.assertFalse(Objects.equals(history1, history2)); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/580e920a/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AlertReceivedListenerTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AlertReceivedListenerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AlertReceivedListenerTest.java index 7bf11e3..7aef175 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AlertReceivedListenerTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/state/alerts/AlertReceivedListenerTest.java @@ -19,6 +19,7 @@ package org.apache.ambari.server.state.alerts; import static org.junit.Assert.assertEquals; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -887,4 +888,48 @@ public class AlertReceivedListenerTest { assertEquals(3, (long) allCurrent.get(0).getOccurrences()); assertEquals(AlertFirmness.HARD, allCurrent.get(0).getFirmness()); } + + /** + * Tests that multiple threads can't create duplicate new alerts. This will + * spawn several threads, each one trying to create the same alert. + */ + @Test + public void testMultipleNewAlertEvents() throws Exception { + assertEquals(0, m_dao.findCurrent().size()); + + final String definitionName = ALERT_DEFINITION + "1"; + List<Thread> threads = new ArrayList<>(); + final AlertReceivedListener listener = m_injector.getInstance(AlertReceivedListener.class); + + // spawn a bunch of concurrent threasd which will try to create teh same + // alert over and over + for (int i = 0; i < 10; i++) { + Thread thread = new Thread() { + /** + * {@inheritDoc} + */ + @Override + public void run() { + Alert alert = new Alert(definitionName, null, "HDFS", null, HOST1, AlertState.OK); + alert.setCluster(m_cluster.getClusterName()); + alert.setLabel(ALERT_LABEL); + alert.setText("HDFS is OK "); + alert.setTimestamp(System.currentTimeMillis()); + + final AlertReceivedEvent event = new AlertReceivedEvent(m_cluster.getClusterId(), alert); + listener.onAlertEvent(event); + } + }; + + threads.add(thread); + thread.start(); + } + + // wait for threads + for (Thread thread : threads) { + thread.join(); + } + + assertEquals(1, m_dao.findCurrent().size()); + } }
