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/26 21:17:23 UTC
ambari git commit: AMBARI-15173 - Express Upgrade Stuck At Manual
Prompt Due To HRC Status Calculation Cache Problem (part2) (jonathanhurley)
Repository: ambari
Updated Branches:
refs/heads/trunk 02cfa6491 -> 73ac59fc3
AMBARI-15173 - Express Upgrade Stuck At Manual Prompt Due To HRC Status Calculation Cache Problem (part2) (jonathanhurley)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/73ac59fc
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/73ac59fc
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/73ac59fc
Branch: refs/heads/trunk
Commit: 73ac59fc33e64cc1443fc19ba5360d047b0e348a
Parents: 02cfa64
Author: Jonathan Hurley <jh...@hortonworks.com>
Authored: Fri Feb 26 12:04:23 2016 -0500
Committer: Jonathan Hurley <jh...@hortonworks.com>
Committed: Fri Feb 26 15:17:01 2016 -0500
----------------------------------------------------------------------
.../persist/jpa/AmbariJpaPersistModule.java | 9 -
.../server/actionmanager/ActionDBAccessor.java | 10 +-
.../actionmanager/ActionDBAccessorImpl.java | 6 +
.../orm/AmbariJpaLocalTxnInterceptor.java | 193 +++++++++++++----
.../orm/TransactionalLockInterceptor.java | 84 --------
.../TransactionalLockInterceptorTest.java | 209 +++++++++++++++++++
.../annotations/TransactionalLockTest.java | 84 +++-----
7 files changed, 404 insertions(+), 191 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/73ac59fc/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 604546c..35b0758 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
@@ -32,10 +32,8 @@ 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;
@@ -94,13 +92,6 @@ 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);
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/73ac59fc/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionDBAccessor.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionDBAccessor.java b/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionDBAccessor.java
index 7f69a31..9aba4c9 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionDBAccessor.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionDBAccessor.java
@@ -26,8 +26,6 @@ import org.apache.ambari.server.agent.CommandReport;
import org.apache.ambari.server.agent.ExecutionCommand;
import org.apache.ambari.server.orm.entities.RequestEntity;
-import com.google.inject.persist.Transactional;
-
public interface ActionDBAccessor {
/**
@@ -88,21 +86,19 @@ public interface ActionDBAccessor {
/**
* Persists all tasks for a given request
- * @param request request object
+ *
+ * @param request
+ * request object
*/
- @Transactional
void persistActions(Request request) throws AmbariException;
- @Transactional
void startRequest(long requestId);
- @Transactional
void endRequest(long requestId);
/**
* Updates request with link to source schedule
*/
- @Transactional
void setSourceScheduleForRequest(long requestId, long scheduleId);
/**
http://git-wip-us.apache.org/repos/asf/ambari/blob/73ac59fc/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 3f4ffeb..003e2e6 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
@@ -29,6 +29,9 @@ import java.util.concurrent.TimeUnit;
import org.apache.ambari.annotations.Experimental;
import org.apache.ambari.annotations.ExperimentalFeature;
+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.AmbariException;
import org.apache.ambari.server.agent.CommandReport;
import org.apache.ambari.server.agent.ExecutionCommand;
@@ -282,6 +285,7 @@ public class ActionDBAccessorImpl implements ActionDBAccessor {
@Override
@Transactional
+ @TransactionalLock(lockArea = LockArea.HRC_STATUS_CACHE, lockType = LockType.WRITE)
public void persistActions(Request request) throws AmbariException {
RequestEntity requestEntity = request.constructNewPersistenceEntity();
@@ -376,6 +380,7 @@ public class ActionDBAccessorImpl implements ActionDBAccessor {
}
@Override
+ @Transactional
public void startRequest(long requestId) {
RequestEntity requestEntity = getRequestEntity(requestId);
if (requestEntity != null && requestEntity.getStartTime() == -1L) {
@@ -385,6 +390,7 @@ public class ActionDBAccessorImpl implements ActionDBAccessor {
}
@Override
+ @Transactional
public void endRequest(long requestId) {
RequestEntity requestEntity = getRequestEntity(requestId);
if (requestEntity != null && requestEntity.getEndTime() == -1L) {
http://git-wip-us.apache.org/repos/asf/ambari/blob/73ac59fc/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 3c953ca..b5442c2 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
@@ -20,6 +20,10 @@ package org.apache.ambari.server.orm;
import java.lang.reflect.Method;
import java.sql.SQLException;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
@@ -27,6 +31,9 @@ import javax.persistence.PersistenceException;
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.eclipse.persistence.exceptions.EclipseLinkException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -36,10 +43,49 @@ import com.google.inject.persist.Transactional;
import com.google.inject.persist.UnitOfWork;
import com.google.inject.persist.jpa.AmbariJpaPersistService;
+/**
+ * The {@link AmbariJpaLocalTxnInterceptor} is used to intercept method calls
+ * annotated with the {@link Transactional} annotation. If a transaction is not
+ * already in progress, then a new transaction is automatically started.
+ * Otherwise, the currently active transaction will be reused.
+ * <p/>
+ * This interceptor also works with {@link TransactionalLock}s to lock on
+ * {@link LockArea}s. If this interceptor encounters a {@link TransactionalLock}
+ * it will acquire the lock and then add the {@link LockArea} to a collection of
+ * areas which need to be released when the transaction is committed or rolled
+ * back. This ensures that transactional methods invoke from an already running
+ * transaction can have their lock invoked for the lifespan of the outer
+ * "parent" transaction.
+ */
public class AmbariJpaLocalTxnInterceptor implements MethodInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(AmbariJpaLocalTxnInterceptor.class);
+ /**
+ * A list of all of the {@link TransactionalLock}s that this interceptor is
+ * responsible for. As a thread moves through the system encountering
+ * {@link Transactional} and {@link TransactionalLock} methods, this will keep
+ * track of which locks the outer-most interceptor will need to release.
+ */
+ private static final ThreadLocal<LinkedList<TransactionalLock>> s_transactionalLocks = new ThreadLocal<LinkedList<TransactionalLock>>() {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected LinkedList<TransactionalLock> initialValue() {
+ return new LinkedList<>();
+ }
+ };
+
+ /**
+ * 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;
+
@Inject
private final AmbariJpaPersistService emProvider = null;
@@ -49,6 +95,9 @@ public class AmbariJpaLocalTxnInterceptor implements MethodInterceptor {
// Tracks if the unit of work was begun implicitly by this transaction.
private final ThreadLocal<Boolean> didWeStartWork = new ThreadLocal<Boolean>();
+ /**
+ * {@inheritDoc}
+ */
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
@@ -61,56 +110,64 @@ public class AmbariJpaLocalTxnInterceptor implements MethodInterceptor {
Transactional transactional = readTransactionMetadata(methodInvocation);
EntityManager em = emProvider.get();
+ // lock the transaction if needed
+ lockTransaction(methodInvocation);
+
// Allow 'joining' of transactions if there is an enclosing @Transactional method.
if (em.getTransaction().isActive()) {
return methodInvocation.proceed();
}
- Object result;
+ try {
+ // this is the outer-most transactional, begin a transaction
+ final EntityTransaction txn = em.getTransaction();
+ txn.begin();
+
+ Object result;
+ try {
+ result = methodInvocation.proceed();
+
+ } catch (Exception e) {
+ // commit transaction only if rollback didn't occur
+ if (rollbackIfNecessary(transactional, e, txn)) {
+ txn.commit();
+ }
- final EntityTransaction txn = em.getTransaction();
- txn.begin();
+ detailedLogForPersistenceError(e);
- try {
- result = methodInvocation.proceed();
+ // propagate whatever exception is thrown anyway
+ throw e;
+ } finally {
+ // 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();
+ }
+ }
- } catch (Exception e) {
- // commit transaction only if rollback didn't occur
- if (rollbackIfNecessary(transactional, e, txn)) {
+ // 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
+ if (null != didWeStartWork.get()) {
+ didWeStartWork.remove();
+ unitOfWork.end();
+ }
}
- detailedLogForPersistenceError(e);
-
- // propagate whatever exception is thrown anyway
- throw e;
+ // or return result
+ return result;
} finally {
- // 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();
- }
+ // unlock all lock areas for this transaction
+ unlockTransaction();
}
-
- // 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
- if (null != didWeStartWork.get()) {
- didWeStartWork.remove();
- unitOfWork.end();
- }
- }
-
- // or return result
- return result;
}
private void detailedLogForPersistenceError(Exception e) {
@@ -199,6 +256,68 @@ public class AmbariJpaLocalTxnInterceptor implements MethodInterceptor {
return commit;
}
+ /**
+ * Locks the {@link LockArea} specified on the {@link TransactionalLock}
+ * annotation if it exists. If the annotation does not exist, then no work is
+ * done.
+ * <p/>
+ * If a lock is acquired, then {@link #s_transactionalLocks} is updated with
+ * the lock so that the outer-most interceptor can release all locks when the
+ * transaction has completed.
+ *
+ * @param methodInvocation
+ */
+ private void lockTransaction(MethodInvocation methodInvocation) {
+ TransactionalLock annotation = methodInvocation.getMethod().getAnnotation(
+ TransactionalLock.class);
+
+ // no work to do if the annotation is not present
+ if (null == annotation) {
+ return;
+ }
+
+ // there is a lock area, so acquire the lock
+ LockArea lockArea = annotation.lockArea();
+ LockType lockType = annotation.lockType();
+
+ ReadWriteLock rwLock = transactionLocks.getLock(lockArea);
+ Lock lock = lockType == LockType.READ ? rwLock.readLock() : rwLock.writeLock();
+
+ lock.lock();
+
+ // ensure that we add this lock area, otherwise it will never be released
+ // when the outer most transaction is committed
+ s_transactionalLocks.get().add(annotation);
+ }
+
+ /**
+ * Unlocks all {@link LockArea}s associated with this transaction or any of
+ * the child transactions which were joined. The order that the locks are
+ * released is inverted from the order in which they were acquired.
+ */
+ private void unlockTransaction(){
+ LinkedList<TransactionalLock> annotations = s_transactionalLocks.get();
+ if (annotations.isEmpty()) {
+ return;
+ }
+
+ // iterate through all locks which were encountered during the course of
+ // this transaction and release them all now that the transaction is
+ // committed; iterate reverse to unlock the most recently locked areas
+ Iterator<TransactionalLock> iterator = annotations.descendingIterator();
+ while (iterator.hasNext()) {
+ TransactionalLock annotation = iterator.next();
+ LockArea lockArea = annotation.lockArea();
+ LockType lockType = annotation.lockType();
+
+ ReadWriteLock rwLock = transactionLocks.getLock(lockArea);
+ Lock lock = lockType == LockType.READ ? rwLock.readLock() : rwLock.writeLock();
+
+ lock.unlock();
+ iterator.remove();
+ }
+ }
+
@Transactional
private static class Internal {
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/73ac59fc/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
deleted file mode 100644
index 0cf73cb..0000000
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/TransactionalLockInterceptor.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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/73ac59fc/ambari-server/src/test/java/org/apache/ambari/annotations/TransactionalLockInterceptorTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/annotations/TransactionalLockInterceptorTest.java b/ambari-server/src/test/java/org/apache/ambari/annotations/TransactionalLockInterceptorTest.java
new file mode 100644
index 0000000..6ebdc0b
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/annotations/TransactionalLockInterceptorTest.java
@@ -0,0 +1,209 @@
+/*
+ * 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.ArrayList;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+
+import org.apache.ambari.annotations.TransactionalLock.LockArea;
+import org.apache.ambari.annotations.TransactionalLock.LockType;
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.orm.GuiceJpaInitializer;
+import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
+import org.apache.ambari.server.orm.TransactionalLocks;
+import org.apache.ambari.server.orm.dao.HostRoleCommandDAO;
+import org.apache.ambari.server.orm.entities.HostRoleCommandEntity;
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.inject.Binder;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.google.inject.persist.PersistService;
+import com.google.inject.persist.Transactional;
+import com.google.inject.util.Modules;
+
+/**
+ * Tests {@link TransactionalLock} and associated classes.
+ */
+public class TransactionalLockInterceptorTest {
+
+ private Injector m_injector;
+
+ @Before
+ public void setup() throws Exception {
+ m_injector = Guice.createInjector(
+ Modules.override(new InMemoryDefaultTestModule()).with(new MockModule()));
+
+ m_injector.getInstance(GuiceJpaInitializer.class);
+ }
+
+ @After
+ public void teardown() throws AmbariException {
+ m_injector.getInstance(PersistService.class).stop();
+ }
+
+ /**
+ * Tests that the {@link Transactional} and {@link TransactionalLock}
+ * annotations cause the interceptors to lock the right area.
+ *
+ * @throws Throwable
+ */
+ @Test
+ public void testTransactionalLockInvocation() throws Throwable {
+ // create mocks
+ TransactionalLocks transactionalLocks = m_injector.getInstance(TransactionalLocks.class);
+ ReadWriteLock readWriteLock = EasyMock.createStrictMock(ReadWriteLock.class);
+ Lock readLock = EasyMock.createStrictMock(Lock.class);
+ Lock writeLock = EasyMock.createStrictMock(Lock.class);
+
+ // expectations
+ EasyMock.expect(transactionalLocks.getLock(LockArea.HRC_STATUS_CACHE)).andReturn(readWriteLock).times(2);
+ EasyMock.expect(readWriteLock.writeLock()).andReturn(writeLock).times(2);
+ writeLock.lock();
+ EasyMock.expectLastCall().once();
+ writeLock.unlock();
+ EasyMock.expectLastCall().once();
+
+ // replay
+ EasyMock.replay(transactionalLocks, readWriteLock, readLock, writeLock);
+
+ // invoke method with annotations
+ HostRoleCommandDAO hostRoleCommandDAO = m_injector.getInstance(HostRoleCommandDAO.class);
+ hostRoleCommandDAO.mergeAll(new ArrayList<HostRoleCommandEntity>());
+
+ // verify locks are called
+ EasyMock.verify(transactionalLocks, readWriteLock, readLock, writeLock);
+ }
+
+ /**
+ * Tests that a {@link TransactionalLock} called within the constructs of an
+ * earlier transaction will still lock.
+ *
+ * @throws Throwable
+ */
+ @Test
+ public void testNestedTransactional() throws Throwable {
+ // create mocks
+ TransactionalLocks transactionalLocks = m_injector.getInstance(TransactionalLocks.class);
+ ReadWriteLock readWriteLock = EasyMock.createStrictMock(ReadWriteLock.class);
+ Lock readLock = EasyMock.createStrictMock(Lock.class);
+ Lock writeLock = EasyMock.createStrictMock(Lock.class);
+
+ // expectations
+ EasyMock.expect(transactionalLocks.getLock(LockArea.HRC_STATUS_CACHE)).andReturn(readWriteLock).times(2);
+ EasyMock.expect(readWriteLock.writeLock()).andReturn(writeLock).times(2);
+ writeLock.lock();
+ EasyMock.expectLastCall().once();
+ writeLock.unlock();
+ EasyMock.expectLastCall().once();
+
+ // replay
+ EasyMock.replay(transactionalLocks, readWriteLock, readLock, writeLock);
+
+ // invoke method with annotations
+ TestObject testObject = m_injector.getInstance(TestObject.class);
+ testObject.testNestedLockMethod();
+
+ // verify locks are called
+ EasyMock.verify(transactionalLocks, readWriteLock, readLock, writeLock);
+ }
+
+ /**
+ * Tests that a {@link TransactionalLock} called within the constructs of an
+ * earlier transaction will still lock.
+ *
+ * @throws Throwable
+ */
+ @Test
+ public void testMultipleLocks() throws Throwable {
+ // create mocks
+ TransactionalLocks transactionalLocks = m_injector.getInstance(TransactionalLocks.class);
+ ReadWriteLock readWriteLock = EasyMock.createStrictMock(ReadWriteLock.class);
+ Lock readLock = EasyMock.createStrictMock(Lock.class);
+ Lock writeLock = EasyMock.createStrictMock(Lock.class);
+
+ // expectations
+ EasyMock.expect(transactionalLocks.getLock(LockArea.HRC_STATUS_CACHE)).andReturn(readWriteLock).times(2);
+ EasyMock.expect(readWriteLock.writeLock()).andReturn(writeLock).times(2);
+ writeLock.lock();
+ EasyMock.expectLastCall().once();
+ writeLock.unlock();
+ EasyMock.expectLastCall().once();
+
+ // another round of expectations
+ EasyMock.expect(transactionalLocks.getLock(LockArea.HRC_STATUS_CACHE)).andReturn(readWriteLock).times(2);
+ EasyMock.expect(readWriteLock.writeLock()).andReturn(writeLock).times(2);
+ writeLock.lock();
+ EasyMock.expectLastCall().once();
+ writeLock.unlock();
+ EasyMock.expectLastCall().once();
+
+ // replay
+ EasyMock.replay(transactionalLocks, readWriteLock, readLock, writeLock);
+
+ // invoke method with annotations
+ TestObject testObject = m_injector.getInstance(TestObject.class);
+ testObject.testMultipleLocks();
+
+ // verify locks are called
+ EasyMock.verify(transactionalLocks, readWriteLock, readLock, writeLock);
+ }
+
+ /**
+ * A test object which has methods annotated for use with this test class.
+ */
+ public static class TestObject {
+ public void testNestedLockMethod() {
+ transactionMethod();
+ transactionMethodWithLock();
+ }
+
+ public void testMultipleLocks() {
+ transactionMethodWithLock();
+ transactionMethodWithLock();
+ }
+
+ @Transactional
+ public void transactionMethod() {
+ }
+
+ @Transactional
+ @TransactionalLock(lockArea = LockArea.HRC_STATUS_CACHE, lockType = LockType.WRITE)
+ public void transactionMethodWithLock() {
+ }
+ }
+
+ /**
+ *
+ */
+ private class MockModule implements Module {
+ /**
+ *
+ */
+ @Override
+ public void configure(Binder binder) {
+ binder.bind(TransactionalLocks.class).toInstance(
+ EasyMock.createNiceMock(TransactionalLocks.class));
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/73ac59fc/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
index fbaa343..1862088 100644
--- a/ambari-server/src/test/java/org/apache/ambari/annotations/TransactionalLockTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/annotations/TransactionalLockTest.java
@@ -17,42 +17,20 @@
*/
package org.apache.ambari.annotations;
+import java.lang.reflect.Method;
+import java.util.HashSet;
import java.util.Properties;
-import org.aopalliance.intercept.MethodInvocation;
import org.apache.ambari.annotations.TransactionalLock.LockArea;
+import org.apache.ambari.annotations.TransactionalLock.LockType;
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 {
/**
@@ -86,41 +64,39 @@ public class TransactionalLockTest {
}
/**
- * 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();
+ public void testAnnotationEquality() {
+ HashSet<TransactionalLock> annotations = new HashSet<>();
+
+ int annotationsFound = 0;
+ Method[] methods = getClass().getDeclaredMethods();
+ for (Method method : methods) {
+ TransactionalLock annotation = method.getAnnotation(TransactionalLock.class);
+ if (null != annotation) {
+ annotations.add(annotation);
+ annotationsFound++;
+ }
+ }
+
+ // there should be 3 discovered annotations, but only 2 in the hashset since
+ // they were collapsed
+ Assert.assertEquals(2, annotations.size());
+ Assert.assertEquals(3, annotationsFound);
+ }
- EasyMock.expect(
- ambariJPAInterceptor.invoke(EasyMock.anyObject(MethodInvocation.class))).andReturn(
- object).once();
- EasyMock.replay(ambariJPAInterceptor, lockInterceptor);
+ @TransactionalLock(lockArea = LockArea.HRC_STATUS_CACHE, lockType = LockType.READ)
+ private void transactionalHRCRead() {
+ }
- Injector injector = Guice.createInjector(new InMemoryDefaultTestModule());
- HostRoleCommandDAO hostRoleCommandDAO = injector.getInstance(HostRoleCommandDAO.class);
- hostRoleCommandDAO.create(new HostRoleCommandEntity());
+ @TransactionalLock(lockArea = LockArea.HRC_STATUS_CACHE, lockType = LockType.READ)
+ private void transactionalHRCRead2() {
+ }
- EasyMock.verify(lockInterceptor);
+ @TransactionalLock(lockArea = LockArea.HRC_STATUS_CACHE, lockType = LockType.WRITE)
+ private void transactionalHRCWrite() {
}
-}
+}
\ No newline at end of file