You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by jo...@apache.org on 2016/02/25 19:03:08 UTC
ambari git commit: AMBARI-15173 - Express Upgrade Stuck At Manual
Prompt Due To HRC Status Calculation Cache Problem (jonathanhurley)
Repository: ambari
Updated Branches:
refs/heads/trunk e4aeab5f8 -> 1621a53dc
AMBARI-15173 - Express Upgrade Stuck At Manual Prompt Due To HRC Status Calculation Cache Problem (jonathanhurley)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/1621a53d
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/1621a53d
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/1621a53d
Branch: refs/heads/trunk
Commit: 1621a53dc5820ba1ca5bf5204628b51d341f0caa
Parents: e4aeab5
Author: Jonathan Hurley <jh...@hortonworks.com>
Authored: Wed Feb 24 18:02:00 2016 -0500
Committer: Jonathan Hurley <jh...@hortonworks.com>
Committed: Thu Feb 25 13:02:03 2016 -0500
----------------------------------------------------------------------
.../persist/jpa/AmbariJpaPersistModule.java | 64 ++++---
.../ambari/annotations/TransactionalLock.java | 148 ++++++++++++++++
.../actionmanager/ActionDBAccessorImpl.java | 28 ++-
.../orm/AmbariJpaLocalTxnInterceptor.java | 45 +++--
.../orm/TransactionalLockInterceptor.java | 84 +++++++++
.../ambari/server/orm/TransactionalLocks.java | 170 +++++++++++++++++++
.../server/orm/dao/HostRoleCommandDAO.java | 137 ++++++++++-----
.../stacks/HDP/2.3/upgrades/upgrade-2.4.xml | 8 +-
.../annotations/TransactionalLockTest.java | 126 ++++++++++++++
9 files changed, 710 insertions(+), 100 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/1621a53d/ambari-server/src/main/java/com/google/inject/persist/jpa/AmbariJpaPersistModule.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/com/google/inject/persist/jpa/AmbariJpaPersistModule.java b/ambari-server/src/main/java/com/google/inject/persist/jpa/AmbariJpaPersistModule.java
index 4e4dd35..604546c 100644
--- a/ambari-server/src/main/java/com/google/inject/persist/jpa/AmbariJpaPersistModule.java
+++ b/ambari-server/src/main/java/com/google/inject/persist/jpa/AmbariJpaPersistModule.java
@@ -17,26 +17,9 @@
*/
package com.google.inject.persist.jpa;
-import com.google.common.collect.Lists;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.google.inject.persist.PersistModule;
-import com.google.inject.persist.PersistService;
-import com.google.inject.persist.UnitOfWork;
-import com.google.inject.persist.finder.DynamicFinder;
-import com.google.inject.persist.finder.Finder;
-import com.google.inject.util.Providers;
-import org.aopalliance.intercept.MethodInterceptor;
-import org.aopalliance.intercept.MethodInvocation;
-import org.apache.ambari.server.orm.AmbariJpaLocalTxnInterceptor;
-import org.apache.ambari.server.orm.AmbariLocalSessionInterceptor;
-import org.apache.ambari.server.orm.RequiresSession;
-
import static com.google.inject.matcher.Matchers.annotatedWith;
import static com.google.inject.matcher.Matchers.any;
-import javax.persistence.EntityManager;
-import javax.persistence.EntityManagerFactory;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
@@ -44,6 +27,27 @@ import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Properties;
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.apache.ambari.annotations.TransactionalLock;
+import org.apache.ambari.server.orm.AmbariJpaLocalTxnInterceptor;
+import org.apache.ambari.server.orm.AmbariLocalSessionInterceptor;
+import org.apache.ambari.server.orm.TransactionalLockInterceptor;
+import org.apache.ambari.server.orm.RequiresSession;
+
+import com.google.common.collect.Lists;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.inject.persist.PersistModule;
+import com.google.inject.persist.PersistService;
+import com.google.inject.persist.UnitOfWork;
+import com.google.inject.persist.finder.DynamicFinder;
+import com.google.inject.persist.finder.Finder;
+import com.google.inject.util.Providers;
+
/**
* Copy of guice persist module for local modifications
*/
@@ -60,7 +64,6 @@ public class AmbariJpaPersistModule extends PersistModule {
private Properties properties;
private MethodInterceptor transactionInterceptor;
- private MethodInterceptor sessionInterceptor;
@Override protected void configurePersistence() {
bindConstant().annotatedWith(Jpa.class).to(jpaUnit);
@@ -68,8 +71,7 @@ public class AmbariJpaPersistModule extends PersistModule {
if (null != properties) {
bind(Properties.class).annotatedWith(Jpa.class).toInstance(properties);
} else {
- bind(Properties.class).annotatedWith(Jpa.class)
- .toProvider(Providers.<Properties>of(null));
+ bind(Properties.class).annotatedWith(Jpa.class).toProvider(Providers.<Properties> of(null));
}
bind(AmbariJpaPersistService.class).in(Singleton.class);
@@ -77,16 +79,13 @@ public class AmbariJpaPersistModule extends PersistModule {
bind(PersistService.class).to(AmbariJpaPersistService.class);
bind(UnitOfWork.class).to(AmbariJpaPersistService.class);
bind(EntityManager.class).toProvider(AmbariJpaPersistService.class);
- bind(EntityManagerFactory.class)
- .toProvider(JpaPersistService.EntityManagerFactoryProvider.class);
-
-
+ bind(EntityManagerFactory.class).toProvider(JpaPersistService.EntityManagerFactoryProvider.class);
transactionInterceptor = new AmbariJpaLocalTxnInterceptor();
requestInjection(transactionInterceptor);
- sessionInterceptor = new AmbariLocalSessionInterceptor();
- requestInjection(sessionInterceptor);
+ MethodInterceptor sessionInterceptor = new AmbariLocalSessionInterceptor();
+ requestInjection(sessionInterceptor);
// Bind dynamic finders.
for (Class<?> finder : dynamicFinders) {
@@ -95,6 +94,13 @@ public class AmbariJpaPersistModule extends PersistModule {
bindInterceptor(annotatedWith(RequiresSession.class), any(), sessionInterceptor);
bindInterceptor(any(), annotatedWith(RequiresSession.class), sessionInterceptor);
+
+ // method-level binding for cross-cutting locks
+ // this runs before the base class binds Transactional, so it always runs
+ // first
+ MethodInterceptor lockAwareInterceptor = new TransactionalLockInterceptor();
+ requestInjection(lockAwareInterceptor);
+ bindInterceptor(any(), annotatedWith(TransactionalLock.class), lockAwareInterceptor);
}
@@ -135,6 +141,7 @@ public class AmbariJpaPersistModule extends PersistModule {
@Inject
JpaFinderProxy finderProxy;
+ @Override
public Object invoke(final Object thisObject, final Method method, final Object[] args)
throws Throwable {
@@ -146,22 +153,27 @@ public class AmbariJpaPersistModule extends PersistModule {
}
return finderProxy.invoke(new MethodInvocation() {
+ @Override
public Method getMethod() {
return method;
}
+ @Override
public Object[] getArguments() {
return null == args ? new Object[0] : args;
}
+ @Override
public Object proceed() throws Throwable {
return method.invoke(thisObject, args);
}
+ @Override
public Object getThis() {
throw new UnsupportedOperationException("Bottomless proxies don't expose a this.");
}
+ @Override
public AccessibleObject getStaticPart() {
throw new UnsupportedOperationException();
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/1621a53d/ambari-server/src/main/java/org/apache/ambari/annotations/TransactionalLock.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/annotations/TransactionalLock.java b/ambari-server/src/main/java/org/apache/ambari/annotations/TransactionalLock.java
new file mode 100644
index 0000000..cd961ba
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/annotations/TransactionalLock.java
@@ -0,0 +1,148 @@
+/**
+ * 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.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.concurrent.locks.ReadWriteLock;
+
+import org.apache.ambari.server.configuration.Configuration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.persist.Transactional;
+
+/**
+ * The {@link TransactionalLock} annotation is used to provide advice around a
+ * joinpoint which will invoke a lock. The lock is invoked before the method
+ * begins and is released after it has executed.
+ * <p/>
+ * This is mainly used in combination with {@link Transactional} methods to
+ * provide locking around the entire transaction in order to prevent other
+ * methods from performing work before a transaction has completed.
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD })
+public @interface TransactionalLock {
+
+ /**
+ * The logic unit of work being locked.
+ *
+ * @return
+ */
+ LockArea lockArea();
+
+ /**
+ * @return
+ */
+ LockType lockType();
+
+ /**
+ * The area that the lock is being applied to. There is exactly 1
+ * {@link ReadWriteLock} for every area defined.
+ */
+ public enum LockArea {
+ /**
+ * Joinpoint lock around work performed on caching the host role command
+ * status in a given stage and request.
+ */
+ HRC_STATUS_CACHE(Configuration.SERVER_HRC_STATUS_SUMMARY_CACHE_ENABLED);
+
+ /**
+ * Logger.
+ */
+ private final static Logger LOG = LoggerFactory.getLogger(LockArea.class);
+
+ /**
+ * The property which governs whether the lock area is enabled or disabled.
+ * Because of the inherent nature of deadlocks with interceptors that lock,
+ * it's wise to be able to disable a lock area dynamically.
+ */
+ private String m_configurationProperty;
+
+ /**
+ * {@code true} if the lock area is enabled and should be lockable.
+ */
+ private Boolean m_enabled = null;
+
+ /**
+ * Constructor.
+ *
+ * @param configurationProperty
+ */
+ private LockArea(String configurationProperty) {
+ m_configurationProperty = configurationProperty;
+ }
+
+ /**
+ * Gets whether this {@link LockArea} is enabled.
+ *
+ * @param configuration
+ * the configuration to read from (not {@code null}).
+ * @return {@code true} if enabled, {@code false} otherwise.
+ */
+ public boolean isEnabled(Configuration configuration) {
+ if (null != m_enabled) {
+ return m_enabled.booleanValue();
+ }
+
+ // start with TRUE
+ m_enabled = Boolean.TRUE;
+ String property = configuration.getProperty(m_configurationProperty);
+
+ if (null != property) {
+ try {
+ m_enabled = Boolean.valueOf(property);
+ } catch (Exception exception) {
+ LOG.error("Unable to determine if the lock area {} is enabled, defaulting to TRUE",
+ m_configurationProperty, exception);
+ }
+ }
+
+ LOG.info("LockArea {} is {}", name(), m_enabled ? "enabled" : "disabled");
+ return m_enabled.booleanValue();
+ }
+
+ /**
+ * Used for testing to clean the internal state of enabled. This should not
+ * be used directly.
+ */
+ void clearEnabled() {
+ m_enabled = null;
+ }
+ }
+
+ /**
+ * The type of lock which should be acquired.
+ */
+ public enum LockType {
+ /**
+ * Read Lock.
+ */
+ READ,
+
+ /**
+ * Write lock.
+ */
+ WRITE;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/1621a53d/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionDBAccessorImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionDBAccessorImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionDBAccessorImpl.java
index 23686c3..3f4ffeb 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionDBAccessorImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionDBAccessorImpl.java
@@ -198,7 +198,10 @@ public class ActionDBAccessorImpl implements ActionDBAccessor {
}
}
- hostRoleCommandDAO.mergeAll(commands);
+ // no need to merge if there's nothing to merge
+ if (!commands.isEmpty()) {
+ hostRoleCommandDAO.mergeAll(commands);
+ }
}
/* (non-Javadoc)
@@ -214,7 +217,12 @@ public class ActionDBAccessorImpl implements ActionDBAccessor {
command.setStatus(command.isRetryAllowed() ? HostRoleStatus.HOLDING_TIMEDOUT : HostRoleStatus.TIMEDOUT);
command.setEndTime(now);
}
- hostRoleCommandDAO.mergeAll(commands);
+
+ // no need to merge if there's nothing to merge
+ if (!commands.isEmpty()) {
+ hostRoleCommandDAO.mergeAll(commands);
+ }
+
endRequestIfCompleted(requestId);
}
@@ -475,7 +483,11 @@ public class ActionDBAccessorImpl implements ActionDBAccessor {
}
}
- hostRoleCommandDAO.mergeAll(commandEntities);
+ // no need to merge if there's nothing to merge
+ if (!commandEntities.isEmpty()) {
+ hostRoleCommandDAO.mergeAll(commandEntities);
+ }
+
// Invalidate cache because of updates to ABORTED commands
hostRoleCommandCache.invalidateAll(abortedCommandUpdates);
@@ -526,7 +538,10 @@ public class ActionDBAccessorImpl implements ActionDBAccessor {
command.setExitcode(report.getExitCode());
}
- hostRoleCommandDAO.mergeAll(commands);
+ // no need to merge if there's nothing to merge
+ if (!commands.isEmpty()) {
+ hostRoleCommandDAO.mergeAll(commands);
+ }
if (checkRequest) {
endRequestIfCompleted(requestId);
@@ -723,7 +738,10 @@ public class ActionDBAccessorImpl implements ActionDBAccessor {
task.setEndTime(-1L);
}
- hostRoleCommandDAO.mergeAll(tasks);
+ // no need to merge if there's nothing to merge
+ if (!tasks.isEmpty()) {
+ hostRoleCommandDAO.mergeAll(tasks);
+ }
}
/**
http://git-wip-us.apache.org/repos/asf/ambari/blob/1621a53d/ambari-server/src/main/java/org/apache/ambari/server/orm/AmbariJpaLocalTxnInterceptor.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/AmbariJpaLocalTxnInterceptor.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/AmbariJpaLocalTxnInterceptor.java
index 6d7901c..3c953ca 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/AmbariJpaLocalTxnInterceptor.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/AmbariJpaLocalTxnInterceptor.java
@@ -18,32 +18,38 @@
package org.apache.ambari.server.orm;
-import com.google.inject.Inject;
-import com.google.inject.persist.Transactional;
-import com.google.inject.persist.UnitOfWork;
-import com.google.inject.persist.jpa.AmbariJpaPersistService;
+import java.lang.reflect.Method;
+import java.sql.SQLException;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityTransaction;
+import javax.persistence.PersistenceException;
+
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.eclipse.persistence.exceptions.EclipseLinkException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.persistence.EntityManager;
-import javax.persistence.EntityTransaction;
-import javax.persistence.PersistenceException;
-import java.lang.reflect.Method;
-import java.sql.SQLException;
+import com.google.inject.Inject;
+import com.google.inject.persist.Transactional;
+import com.google.inject.persist.UnitOfWork;
+import com.google.inject.persist.jpa.AmbariJpaPersistService;
public class AmbariJpaLocalTxnInterceptor implements MethodInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(AmbariJpaLocalTxnInterceptor.class);
+
@Inject
private final AmbariJpaPersistService emProvider = null;
+
@Inject
private final UnitOfWork unitOfWork = null;
+
// Tracks if the unit of work was begun implicitly by this transaction.
private final ThreadLocal<Boolean> didWeStartWork = new ThreadLocal<Boolean>();
+ @Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
// Should we start a unit of work?
@@ -53,54 +59,57 @@ public class AmbariJpaLocalTxnInterceptor implements MethodInterceptor {
}
Transactional transactional = readTransactionMetadata(methodInvocation);
- EntityManager em = this.emProvider.get();
+ EntityManager em = emProvider.get();
// Allow 'joining' of transactions if there is an enclosing @Transactional method.
if (em.getTransaction().isActive()) {
return methodInvocation.proceed();
}
+ Object result;
+
final EntityTransaction txn = em.getTransaction();
txn.begin();
- Object result;
try {
result = methodInvocation.proceed();
} catch (Exception e) {
- //commit transaction only if rollback didn't occur
+ // commit transaction only if rollback didn't occur
if (rollbackIfNecessary(transactional, e, txn)) {
txn.commit();
}
detailedLogForPersistenceError(e);
- //propagate whatever exception is thrown anyway
+ // propagate whatever exception is thrown anyway
throw e;
} finally {
- // Close the em if necessary (guarded so this code doesn't run unless catch fired).
+ // Close the em if necessary (guarded so this code doesn't run unless
+ // catch fired).
if (null != didWeStartWork.get() && !txn.isActive()) {
didWeStartWork.remove();
unitOfWork.end();
}
}
- //everything was normal so commit the txn (do not move into try block above as it
- // interferes with the advised method's throwing semantics)
+ // everything was normal so commit the txn (do not move into try block
+ // above as it
+ // interferes with the advised method's throwing semantics)
try {
txn.commit();
} catch (Exception e) {
detailedLogForPersistenceError(e);
throw e;
} finally {
- //close the em if necessary
+ // close the em if necessary
if (null != didWeStartWork.get()) {
didWeStartWork.remove();
unitOfWork.end();
}
}
- //or return result
+ // or return result
return result;
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/1621a53d/ambari-server/src/main/java/org/apache/ambari/server/orm/TransactionalLockInterceptor.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/TransactionalLockInterceptor.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/TransactionalLockInterceptor.java
new file mode 100644
index 0000000..0cf73cb
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/TransactionalLockInterceptor.java
@@ -0,0 +1,84 @@
+/*
+ * 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;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.apache.ambari.annotations.TransactionalLock;
+import org.apache.ambari.annotations.TransactionalLock.LockArea;
+import org.apache.ambari.annotations.TransactionalLock.LockType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.google.inject.persist.Transactional;
+
+/**
+ * The {@link TransactionalLockInterceptor} is a method level intercept which
+ * will use the properties of {@link TransactionalLock} to acquire a
+ * {@link ReadWriteLock} around a particular {@link LockArea}.
+ * <p/>
+ * It is mainly used to provide a lock around an method annotated with
+ * {@link Transactional}. Consider the case where an action must happen after a
+ * method has completed and the transaction has been committed.
+ */
+public class TransactionalLockInterceptor implements MethodInterceptor {
+
+ /**
+ * Logger.
+ */
+ private static final Logger LOG = LoggerFactory.getLogger(TransactionalLockInterceptor.class);
+
+ /**
+ * Used to ensure that methods which rely on the completion of
+ * {@link Transactional} can detect when they are able to run.
+ *
+ * @see TransactionalLock
+ */
+ @Inject
+ private final TransactionalLocks transactionLocks = null;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object invoke(MethodInvocation methodInvocation) throws Throwable {
+
+ TransactionalLock annotation = methodInvocation.getMethod().getAnnotation(
+ TransactionalLock.class);
+
+ LockArea lockArea = annotation.lockArea();
+ LockType lockType = annotation.lockType();
+
+ ReadWriteLock rwLock = transactionLocks.getLock(lockArea);
+ Lock lock = lockType == LockType.READ ? rwLock.readLock() : rwLock.writeLock();
+
+ lock.lock();
+
+ try {
+ Object object = methodInvocation.proceed();
+ return object;
+ } finally {
+ lock.unlock();
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/1621a53d/ambari-server/src/main/java/org/apache/ambari/server/orm/TransactionalLocks.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/TransactionalLocks.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/TransactionalLocks.java
new file mode 100644
index 0000000..1768dd8
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/TransactionalLocks.java
@@ -0,0 +1,170 @@
+/**
+ * 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;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.ambari.annotations.TransactionalLock.LockArea;
+import org.apache.ambari.server.configuration.Configuration;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.inject.persist.Transactional;
+
+/**
+ * The {@link TransactionalLocks} class is used to manage the locks associated
+ * with each {@link LockArea}. It's a singlegon that shoudl always be injected.
+ */
+@Singleton
+public class TransactionalLocks {
+
+ /**
+ * Used to lookup whether {@link LockArea}s are enabled.
+ */
+ @Inject
+ private Configuration m_configuration;
+
+ /**
+ * Manages the locks for each class which uses the {@link Transactional}
+ * annotation.
+ */
+ private final ConcurrentHashMap<LockArea, ReadWriteLock> m_locks = new ConcurrentHashMap<>();
+
+ /**
+ * Gets a lock for the specified lock area. There is a 1:1 relationship
+ * between a lock area and a lock.
+ * <p/>
+ * If the {@link LockArea} is not enabled, then this will return an empty
+ * {@link Lock} implementation which doesn't actually lock anything.
+ *
+ * @param lockArea
+ * the lock area to get the lock for (not {@code null}).
+ * @return the lock to use for the specified lock area (never {@code null}).
+ */
+ public ReadWriteLock getLock(LockArea lockArea) {
+ ReadWriteLock lock = m_locks.get(lockArea);
+ if (null == lock) {
+ if (lockArea.isEnabled(m_configuration)) {
+ lock = new ReentrantReadWriteLock(true);
+ } else {
+ lock = new NoOperationReadWriteLock();
+ }
+
+ m_locks.put(lockArea, lock);
+ }
+
+ return lock;
+ }
+
+ /**
+ * A dummy implementation of a {@link ReadWriteLock} that returns locks which
+ * only NOOP. This is used for cases where dependant code doesn't want to
+ * {@code if/else} all over the place.
+ */
+ private final static class NoOperationReadWriteLock implements ReadWriteLock {
+
+ private final Lock m_readLock = new NoOperationLock();
+ private final Lock m_writeLock = new NoOperationLock();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Lock readLock() {
+ return m_readLock;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Lock writeLock() {
+ return m_writeLock;
+ }
+ }
+
+ /**
+ * A dummy implementation of a {@link Lock} that only NOOPs. This is used for
+ * cases where dependant code doesn't want to {@code if/else} all over the
+ * place.
+ */
+ private final static class NoOperationLock implements Lock {
+
+ /**
+ * NOOP
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public void lock() {
+ }
+
+ /**
+ * NOOP
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public void lockInterruptibly() throws InterruptedException {
+ }
+
+ /**
+ * NOOP, returns {@code true} always.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean tryLock() {
+ return true;
+ }
+
+ /**
+ * NOOP, returns {@code true} always.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
+ return true;
+ }
+
+ /**
+ * NOOP
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public void unlock() {
+ }
+
+ @Override
+ /**
+ * NOOP
+ * <p/>
+ * {@inheritDoc}
+ */
+ public Condition newCondition() {
+ return null;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/1621a53d/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/HostRoleCommandDAO.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/HostRoleCommandDAO.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/HostRoleCommandDAO.java
index deca9b1..c2ded2f 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/HostRoleCommandDAO.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/HostRoleCommandDAO.java
@@ -18,6 +18,9 @@
package org.apache.ambari.server.orm.dao;
+import static org.apache.ambari.server.orm.DBAccessor.DbType.ORACLE;
+import static org.apache.ambari.server.orm.dao.DaoUtils.ORACLE_LIST_LIMIT;
+
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
@@ -26,6 +29,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReadWriteLock;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
@@ -33,6 +37,9 @@ import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.metamodel.SingularAttribute;
+import org.apache.ambari.annotations.TransactionalLock;
+import org.apache.ambari.annotations.TransactionalLock.LockArea;
+import org.apache.ambari.annotations.TransactionalLock.LockType;
import org.apache.ambari.server.RoleCommand;
import org.apache.ambari.server.actionmanager.HostRoleStatus;
import org.apache.ambari.server.api.query.JpaPredicateVisitor;
@@ -43,6 +50,7 @@ import org.apache.ambari.server.controller.spi.Request;
import org.apache.ambari.server.controller.spi.SortRequest;
import org.apache.ambari.server.controller.utilities.PredicateHelper;
import org.apache.ambari.server.orm.RequiresSession;
+import org.apache.ambari.server.orm.TransactionalLocks;
import org.apache.ambari.server.orm.entities.HostEntity;
import org.apache.ambari.server.orm.entities.HostRoleCommandEntity;
import org.apache.ambari.server.orm.entities.HostRoleCommandEntity_;
@@ -60,9 +68,6 @@ import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.google.inject.persist.Transactional;
-import static org.apache.ambari.server.orm.DBAccessor.DbType.ORACLE;
-import static org.apache.ambari.server.orm.dao.DaoUtils.ORACLE_LIST_LIMIT;
-
@Singleton
public class HostRoleCommandDAO {
@@ -102,9 +107,17 @@ public class HostRoleCommandDAO {
private static final String COMPLETED_REQUESTS_SQL = "SELECT DISTINCT task.requestId FROM HostRoleCommandEntity task WHERE task.requestId NOT IN (SELECT task.requestId FROM HostRoleCommandEntity task WHERE task.status IN :notCompletedStatuses) ORDER BY task.requestId {0}";
/**
- * A cache that holds {@link HostRoleCommandStatusSummaryDTO} grouped by stage id for requests by request id.
- * The JPQL computing the host role command status summary for a request is rather expensive
- * thus this cache helps reducing the load on the database
+ * A cache that holds {@link HostRoleCommandStatusSummaryDTO} grouped by stage
+ * id for requests by request id. The JPQL computing the host role command
+ * status summary for a request is rather expensive thus this cache helps
+ * reducing the load on the database.
+ * <p/>
+ * Methods which interact with this cache, including invalidation and
+ * population, should use the {@link TransactionalLock} annotation along with
+ * the {@link LockArea#HRC_STATUS_CACHE}. This will prevent stale data from
+ * being read during a transaction which has updated a
+ * {@link HostRoleCommandEntity}'s {@link HostRoleStatus} but has not
+ * committed yet.
*/
private final LoadingCache<Long, Map<Long, HostRoleCommandStatusSummaryDTO>> hrcStatusSummaryCache;
@@ -121,6 +134,15 @@ public class HostRoleCommandDAO {
@Inject
DaoUtils daoUtils;
+ /**
+ * Used to ensure that methods which rely on the completion of
+ * {@link Transactional} can detect when they are able to run.
+ *
+ * @see TransactionalLock
+ */
+ @Inject
+ private final TransactionalLocks transactionLocks = null;
+
public final static String HRC_STATUS_SUMMARY_CACHE_SIZE = "hostRoleCommandStatusSummaryCacheSize";
public final static String HRC_STATUS_SUMMARY_CACHE_EXPIRY_DURATION_MINUTES = "hostRoleCommandStatusCacheExpiryDurationMins";
public final static String HRC_STATUS_SUMMARY_CACHE_ENABLED = "hostRoleCommandStatusSummaryCacheEnabled";
@@ -130,12 +152,12 @@ public class HostRoleCommandDAO {
* @param requestId the key of the cache entry to be invalidated.
*/
protected void invalidateHostRoleCommandStatusSummaryCache(Long requestId) {
- if (!hostRoleCommandStatusSummaryCacheEnabled )
+ if (!hostRoleCommandStatusSummaryCacheEnabled ) {
return;
+ }
LOG.debug("Invalidating host role command status summary cache for request {} !", requestId);
hrcStatusSummaryCache.invalidate(requestId);
-
}
/**
@@ -144,21 +166,23 @@ public class HostRoleCommandDAO {
* @param hostRoleCommandEntity
*/
protected void invalidateHostRoleCommandStatusCache(HostRoleCommandEntity hostRoleCommandEntity) {
- if ( !hostRoleCommandStatusSummaryCacheEnabled )
+ if ( !hostRoleCommandStatusSummaryCacheEnabled ) {
return;
+ }
if (hostRoleCommandEntity != null) {
Long requestId = hostRoleCommandEntity.getRequestId();
if (requestId == null) {
StageEntity stageEntity = hostRoleCommandEntity.getStage();
- if (stageEntity != null)
+ if (stageEntity != null) {
requestId = stageEntity.getRequestId();
+ }
}
- if (requestId != null)
+ if (requestId != null) {
invalidateHostRoleCommandStatusSummaryCache(requestId.longValue());
+ }
}
-
}
/**
@@ -170,42 +194,52 @@ public class HostRoleCommandDAO {
*/
@RequiresSession
protected Map<Long, HostRoleCommandStatusSummaryDTO> loadAggregateCounts(Long requestId) {
-
- TypedQuery<HostRoleCommandStatusSummaryDTO> query = entityManagerProvider.get().createQuery(
- SUMMARY_DTO, HostRoleCommandStatusSummaryDTO.class);
-
- query.setParameter("requestId", requestId);
- query.setParameter("aborted", HostRoleStatus.ABORTED);
- query.setParameter("completed", HostRoleStatus.COMPLETED);
- query.setParameter("failed", HostRoleStatus.FAILED);
- query.setParameter("holding", HostRoleStatus.HOLDING);
- query.setParameter("holding_failed", HostRoleStatus.HOLDING_FAILED);
- query.setParameter("holding_timedout", HostRoleStatus.HOLDING_TIMEDOUT);
- query.setParameter("in_progress", HostRoleStatus.IN_PROGRESS);
- query.setParameter("pending", HostRoleStatus.PENDING);
- query.setParameter("queued", HostRoleStatus.QUEUED);
- query.setParameter("timedout", HostRoleStatus.TIMEDOUT);
- query.setParameter("skipped_failed", HostRoleStatus.SKIPPED_FAILED);
-
Map<Long, HostRoleCommandStatusSummaryDTO> map = new HashMap<Long, HostRoleCommandStatusSummaryDTO>();
- for (HostRoleCommandStatusSummaryDTO dto : daoUtils.selectList(query)) {
- map.put(dto.getStageId(), dto);
+ // ensure that we wait for any running transactions working on this cache to
+ // complete
+ ReadWriteLock lock = transactionLocks.getLock(LockArea.HRC_STATUS_CACHE);
+ lock.readLock().lock();
+
+ try {
+ TypedQuery<HostRoleCommandStatusSummaryDTO> query = entityManagerProvider.get().createQuery(
+ SUMMARY_DTO, HostRoleCommandStatusSummaryDTO.class);
+
+ query.setParameter("requestId", requestId);
+ query.setParameter("aborted", HostRoleStatus.ABORTED);
+ query.setParameter("completed", HostRoleStatus.COMPLETED);
+ query.setParameter("failed", HostRoleStatus.FAILED);
+ query.setParameter("holding", HostRoleStatus.HOLDING);
+ query.setParameter("holding_failed", HostRoleStatus.HOLDING_FAILED);
+ query.setParameter("holding_timedout", HostRoleStatus.HOLDING_TIMEDOUT);
+ query.setParameter("in_progress", HostRoleStatus.IN_PROGRESS);
+ query.setParameter("pending", HostRoleStatus.PENDING);
+ query.setParameter("queued", HostRoleStatus.QUEUED);
+ query.setParameter("timedout", HostRoleStatus.TIMEDOUT);
+ query.setParameter("skipped_failed", HostRoleStatus.SKIPPED_FAILED);
+
+ for (HostRoleCommandStatusSummaryDTO dto : daoUtils.selectList(query)) {
+ map.put(dto.getStageId(), dto);
+ }
+ } finally {
+ lock.readLock().unlock();
}
return map;
}
@Inject
- public HostRoleCommandDAO(@Named(HRC_STATUS_SUMMARY_CACHE_ENABLED) boolean hostRoleCommandStatusSummaryCacheEnabled, @Named(HRC_STATUS_SUMMARY_CACHE_SIZE) long hostRoleCommandStatusSummaryCacheLimit, @Named(HRC_STATUS_SUMMARY_CACHE_EXPIRY_DURATION_MINUTES) long hostRoleCommandStatusSummaryCacheExpiryDurationMins) {
+ public HostRoleCommandDAO(
+ @Named(HRC_STATUS_SUMMARY_CACHE_ENABLED) boolean hostRoleCommandStatusSummaryCacheEnabled,
+ @Named(HRC_STATUS_SUMMARY_CACHE_SIZE) long hostRoleCommandStatusSummaryCacheLimit,
+ @Named(HRC_STATUS_SUMMARY_CACHE_EXPIRY_DURATION_MINUTES) long hostRoleCommandStatusSummaryCacheExpiryDurationMins) {
this.hostRoleCommandStatusSummaryCacheEnabled = hostRoleCommandStatusSummaryCacheEnabled;
LOG.info("Host role command status summary cache {} !", hostRoleCommandStatusSummaryCacheEnabled ? "enabled" : "disabled");
-
hrcStatusSummaryCache = CacheBuilder.newBuilder()
.maximumSize(hostRoleCommandStatusSummaryCacheLimit)
- .expireAfterAccess(hostRoleCommandStatusSummaryCacheExpiryDurationMins, TimeUnit.MINUTES)
+ .expireAfterWrite(hostRoleCommandStatusSummaryCacheExpiryDurationMins, TimeUnit.MINUTES)
.build(new CacheLoader<Long, Map<Long, HostRoleCommandStatusSummaryDTO>>() {
@Override
public Map<Long, HostRoleCommandStatusSummaryDTO> load(Long requestId) throws Exception {
@@ -542,15 +576,19 @@ public class HostRoleCommandDAO {
}
@Transactional
- public void create(HostRoleCommandEntity stageEntity) {
- entityManagerProvider.get().persist(stageEntity);
+ @TransactionalLock(lockArea = LockArea.HRC_STATUS_CACHE, lockType = LockType.WRITE)
+ public void create(HostRoleCommandEntity entity) {
+ EntityManager entityManager = entityManagerProvider.get();
+ entityManager.persist(entity);
- invalidateHostRoleCommandStatusCache(stageEntity);
+ invalidateHostRoleCommandStatusCache(entity);
}
@Transactional
- public HostRoleCommandEntity merge(HostRoleCommandEntity stageEntity) {
- HostRoleCommandEntity entity = entityManagerProvider.get().merge(stageEntity);
+ @TransactionalLock(lockArea = LockArea.HRC_STATUS_CACHE, lockType = LockType.WRITE)
+ public HostRoleCommandEntity merge(HostRoleCommandEntity entity) {
+ EntityManager entityManager = entityManagerProvider.get();
+ entity = entityManager.merge(entity);
invalidateHostRoleCommandStatusCache(entity);
@@ -566,21 +604,24 @@ public class HostRoleCommandDAO {
}
@Transactional
+ @TransactionalLock(lockArea = LockArea.HRC_STATUS_CACHE, lockType = LockType.WRITE)
public List<HostRoleCommandEntity> mergeAll(Collection<HostRoleCommandEntity> entities) {
List<HostRoleCommandEntity> managedList = new ArrayList<HostRoleCommandEntity>(entities.size());
for (HostRoleCommandEntity entity : entities) {
- managedList.add(entityManagerProvider.get().merge(entity));
-
+ EntityManager entityManager = entityManagerProvider.get();
+ managedList.add(entityManager.merge(entity));
invalidateHostRoleCommandStatusCache(entity);
}
+
return managedList;
}
@Transactional
- public void remove(HostRoleCommandEntity stageEntity) {
- entityManagerProvider.get().remove(merge(stageEntity));
-
- invalidateHostRoleCommandStatusCache(stageEntity);
+ @TransactionalLock(lockArea = LockArea.HRC_STATUS_CACHE, lockType = LockType.WRITE)
+ public void remove(HostRoleCommandEntity entity) {
+ EntityManager entityManager = entityManagerProvider.get();
+ entityManager.remove(merge(entity));
+ invalidateHostRoleCommandStatusCache(entity);
}
@Transactional
@@ -595,10 +636,12 @@ public class HostRoleCommandDAO {
* @return the map of stage-to-summary objects
*/
public Map<Long, HostRoleCommandStatusSummaryDTO> findAggregateCounts(Long requestId) {
- if (hostRoleCommandStatusSummaryCacheEnabled)
+ if (hostRoleCommandStatusSummaryCacheEnabled) {
return hrcStatusSummaryCache.getUnchecked(requestId);
- else
+ }
+ else {
return loadAggregateCounts(requestId); // if caching not enabled fall back to fetching through JPA
+ }
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/1621a53d/ambari-server/src/main/resources/stacks/HDP/2.3/upgrades/upgrade-2.4.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/stacks/HDP/2.3/upgrades/upgrade-2.4.xml b/ambari-server/src/main/resources/stacks/HDP/2.3/upgrades/upgrade-2.4.xml
index 29ebc1f..f775314 100644
--- a/ambari-server/src/main/resources/stacks/HDP/2.3/upgrades/upgrade-2.4.xml
+++ b/ambari-server/src/main/resources/stacks/HDP/2.3/upgrades/upgrade-2.4.xml
@@ -370,7 +370,7 @@
<group xsi:type="cluster" name="FINALIZE_PRE_CHECK" title="Finalize {{direction.text.proper}} Pre-Check">
<direction>UPGRADE</direction>
-
+
<execute-stage title="Check Component Versions">
<task xsi:type="server_action" class="org.apache.ambari.server.serveraction.upgrades.ComponentVersionCheckAction" />
</execute-stage>
@@ -548,7 +548,7 @@
<task xsi:type="restart-task" />
</upgrade>
</component>
-
+
<component name="PHOENIX_QUERY_SERVER">
<upgrade>
<task xsi:type="restart-task"/>
@@ -688,7 +688,7 @@
<task xsi:type="configure" id="hdp_2_4_0_0_oozie_remove_service_classes" />
<task xsi:type="server_action" summary="Adjusting Oozie properties" class="org.apache.ambari.server.serveraction.upgrades.OozieConfigCalculation"/>
-
+
<task xsi:type="execute" hosts="all" sequential="true" summary="Shut down all Oozie servers">
<script>scripts/oozie_server.py</script>
<function>stop</function>
@@ -745,7 +745,7 @@
<pre-upgrade>
<task xsi:type="configure" id ="hdp_2_4_0_0_kafka_broker_deprecate_port"/>
</pre-upgrade>
-
+
<upgrade>
<task xsi:type="restart-task" />
</upgrade>
http://git-wip-us.apache.org/repos/asf/ambari/blob/1621a53d/ambari-server/src/test/java/org/apache/ambari/annotations/TransactionalLockTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/annotations/TransactionalLockTest.java b/ambari-server/src/test/java/org/apache/ambari/annotations/TransactionalLockTest.java
new file mode 100644
index 0000000..fbaa343
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/annotations/TransactionalLockTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.annotations;
+
+import java.util.Properties;
+
+import org.aopalliance.intercept.MethodInvocation;
+import org.apache.ambari.annotations.TransactionalLock.LockArea;
+import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.orm.AmbariJpaLocalTxnInterceptor;
+import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
+import org.apache.ambari.server.orm.TransactionalLockInterceptor;
+import org.apache.ambari.server.orm.dao.HostRoleCommandDAO;
+import org.apache.ambari.server.orm.entities.HostRoleCommandEntity;
+import org.easymock.EasyMock;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.api.easymock.PowerMock;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.persist.Transactional;
+import com.google.inject.persist.jpa.AmbariJpaPersistModule;
+import com.google.inject.persist.jpa.AmbariJpaPersistService;
+
+import junit.framework.Assert;
+
+/**
+ * Tests {@link TransactionalLock} and associated classes.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(
+ value = { HostRoleCommandDAO.class, AmbariJpaLocalTxnInterceptor.class,
+ TransactionalLockInterceptor.class, AmbariJpaPersistModule.class,
+ AmbariJpaPersistService.class })
+@PowerMockIgnore("javax.management.*")
+public class TransactionalLockTest {
+
+ /**
+ * Tests that {@link LockArea} is correctly enabled/disabled.
+ */
+ @Test
+ public void testLockAreaEnabled() throws Exception {
+ final Properties ambariProperties = new Properties();
+ ambariProperties.put(Configuration.SERVER_HRC_STATUS_SUMMARY_CACHE_ENABLED, "true");
+ Configuration configuration = new Configuration(ambariProperties);
+
+ LockArea lockArea = LockArea.HRC_STATUS_CACHE;
+ lockArea.clearEnabled();
+
+ Assert.assertTrue(lockArea.isEnabled(configuration));
+ }
+
+ /**
+ * Tests that {@link LockArea} is correctly enabled/disabled.
+ */
+ @Test
+ public void testLockAreaEnabledDisabled() throws Exception {
+ final Properties ambariProperties = new Properties();
+ ambariProperties.put(Configuration.SERVER_HRC_STATUS_SUMMARY_CACHE_ENABLED, "false");
+ Configuration configuration = new Configuration(ambariProperties);
+
+ LockArea lockArea = LockArea.HRC_STATUS_CACHE;
+ lockArea.clearEnabled();
+
+ Assert.assertFalse(lockArea.isEnabled(configuration));
+ }
+
+ /**
+ * Tests that the {@link Transactional} and {@link TransactionalLock}
+ * annotations cause the interceptors to be called in the correct order.
+ *
+ * @throws Throwable
+ */
+ @Test
+ public void testTransactionLockOrdering() throws Throwable {
+ AmbariJpaLocalTxnInterceptor ambariJPAInterceptor = PowerMock.createNiceMock(
+ AmbariJpaLocalTxnInterceptor.class);
+
+ TransactionalLockInterceptor lockInterceptor = PowerMock.createNiceMock(
+ TransactionalLockInterceptor.class);
+
+ PowerMockito.whenNew(AmbariJpaLocalTxnInterceptor.class).withAnyArguments().thenReturn(
+ ambariJPAInterceptor);
+
+ PowerMockito.whenNew(TransactionalLockInterceptor.class).withAnyArguments().thenReturn(
+ lockInterceptor);
+
+ Object object = new Object();
+
+ EasyMock.expect(lockInterceptor.invoke(EasyMock.anyObject(MethodInvocation.class))).andReturn(
+ object).once();
+
+ EasyMock.expect(
+ ambariJPAInterceptor.invoke(EasyMock.anyObject(MethodInvocation.class))).andReturn(
+ object).once();
+
+ EasyMock.replay(ambariJPAInterceptor, lockInterceptor);
+
+ Injector injector = Guice.createInjector(new InMemoryDefaultTestModule());
+ HostRoleCommandDAO hostRoleCommandDAO = injector.getInstance(HostRoleCommandDAO.class);
+ hostRoleCommandDAO.create(new HostRoleCommandEntity());
+
+ EasyMock.verify(lockInterceptor);
+ }
+
+}