package org.ovirt.engine.core.bll.network.macpool; import static java.util.Collections.singletonList; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.transaction.HeuristicMixedException; import javax.transaction.HeuristicRollbackException; import javax.transaction.RollbackException; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import javax.transaction.xa.XAResource; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.AdditionalAnswers; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.context.EngineContext; import org.ovirt.engine.core.di.InjectorRule; import org.ovirt.engine.core.utils.lock.LockedObjectFactory; @RunWith(MockitoJUnitRunner.class) public class TransactionalMacPoolDecoratorRollbackTest { @Rule public ExpectedException expectedException = ExpectedException.none(); @ClassRule public static InjectorRule injectorRule = new InjectorRule(); @Mock private LockedObjectFactory lockedObjectFactory; @Mock private TransactionManager transactionManager; @Mock private MacPool sourceMacPool; @Mock private MacPool targetMacPool; private Transaction transaction = new TransactionStub(); private final CommandContext commandContext = new CommandContext(new EngineContext()); private static final List<String> SOURCE_POOL_MACS = Arrays.asList("90:2b:34:d6:72:49", "90:2b:34:d6:72:4a"); @Test public void testUnsuccessfulMigrationRevertsToOriginalState() throws Exception { injectorRule.bind(TransactionManager.class, transactionManager); when(transactionManager.getTransaction()).thenReturn(transaction); mockLockObjectFactoryToDisableLocking(); mockThatDuringAddingToTargetPoolOnlyFirstMacWillBeAdded(); DecoratedMacPoolFactory decoratedMacPoolFactory = new DecoratedMacPoolFactory(this.lockedObjectFactory); MacPool decoratedSourceMacPool = createDecoratedPool(decoratedMacPoolFactory, sourceMacPool); MacPool decoratedTargetMacPool = createDecoratedPool(decoratedMacPoolFactory, targetMacPool); decoratedSourceMacPool.freeMacs(SOURCE_POOL_MACS); decoratedTargetMacPool.addMacs(SOURCE_POOL_MACS); verify(sourceMacPool).getId(); verify(targetMacPool).getId(); //related to releasing macs. verify(sourceMacPool, times(2)).isMacInUse(anyString()); //actual freing won't be invoked, macs are being held until TX end. verify(sourceMacPool, never()).freeMacs(SOURCE_POOL_MACS); verify(targetMacPool).addMacs(SOURCE_POOL_MACS); verifyNoMoreInteractions(sourceMacPool, targetMacPool); //simulates TX rollback transaction.rollback(); //no macs were actually released(release was waiting for commit), thus no macs will be added back. verify(sourceMacPool, never()).addMac(anyString()); verify(sourceMacPool, never()).addMacs(anyList()); //because we mocked, that second mac won't be added due to duplicity, only first one will be released. verify(targetMacPool).freeMacs(Collections.singletonList(SOURCE_POOL_MACS.get(0))); verifyNoMoreInteractions(sourceMacPool, targetMacPool); } /** * … the other one won't be added, due to duplicity */ private void mockThatDuringAddingToTargetPoolOnlyFirstMacWillBeAdded() { when(targetMacPool.addMacs(anyList())).thenAnswer(invocation -> { List<String> macs = invocation.getArgument(0); return Collections.singletonList(macs.get(1)); }); } private void mockLockObjectFactoryToDisableLocking() { when(lockedObjectFactory .createLockingInstance(any(MacPool.class), eq(MacPool.class), any(ReentrantReadWriteLock.class))) .thenAnswer(AdditionalAnswers.returnsArgAt(0)); } private MacPool createDecoratedPool(DecoratedMacPoolFactory decoratedMacPoolFactory, MacPool pool) { List<MacPoolDecorator> decorators = singletonList(new TransactionalMacPoolDecorator(commandContext)); return decoratedMacPoolFactory.createDecoratedPool(pool, decorators); } private static class TransactionStub implements Transaction { private Synchronization sync; @Override public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException, IllegalStateException, SystemException { } @Override public boolean delistResource(XAResource xaRes, int flag) throws IllegalStateException, SystemException { throw new UnsupportedOperationException("Not implemented yet"); } @Override public boolean enlistResource(XAResource xaRes) throws RollbackException, IllegalStateException, SystemException { throw new UnsupportedOperationException("Not implemented yet"); } @Override public int getStatus() throws SystemException { return Status.STATUS_ROLLEDBACK; } @Override public void registerSynchronization(Synchronization sync) throws RollbackException, IllegalStateException, SystemException { this.sync = sync; } @Override public void rollback() throws IllegalStateException, SystemException { this.sync.afterCompletion(getStatus()); } @Override public void setRollbackOnly() throws IllegalStateException, SystemException { throw new UnsupportedOperationException("Not implemented yet"); } } }