package org.ovirt.engine.core.bll.network.macpool;
import static java.util.stream.Collectors.toList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.context.CompensationContext;
import org.ovirt.engine.core.common.businessentities.ReleaseMacsTransientCompensation;
import org.ovirt.engine.core.common.utils.ToStringBuilder;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.utils.transaction.NoOpTransactionCompletionListener;
import org.ovirt.engine.core.utils.transaction.TransactionCompletionListener;
import org.ovirt.engine.core.utils.transaction.TransactionSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class TransactionalMacPoolDecorator extends DelegatingMacPoolDecorator implements MacPoolDecorator {
private static final Logger log = LoggerFactory.getLogger(TransactionalMacPoolDecorator.class);
private final UsingCompensationState usingCompensationState;
private final UsingTxDecoratorState usingTxDecoratorState;
private final NontransactionalState nontransactionalState;
public TransactionalMacPoolDecorator(CommandContext commandContext) {
usingCompensationState = new UsingCompensationState(commandContext);
usingTxDecoratorState = new UsingTxDecoratorState();
nontransactionalState = new NontransactionalState();
}
private TransactionalStrategyState getStrategyForMacRelease() {
if (usingCompensationState.shouldUseCompensation()) {
return usingCompensationState;
}
boolean shouldUseTxDecorator = TransactionSupport.current() != null;
if (shouldUseTxDecorator) {
return usingTxDecoratorState;
}
return nontransactionalState;
}
private List<TransactionalStrategyState> getStrategyForMacAllocation() {
List<TransactionalStrategyState> states = new ArrayList<>();
if (usingCompensationState.shouldUseCompensation()) {
states.add(usingCompensationState);
}
boolean shouldUseTxDecorator = TransactionSupport.current() != null;
if (shouldUseTxDecorator) {
states.add(usingTxDecoratorState);
}
if (states.isEmpty()) {
states.add(nontransactionalState);
}
log.debug("Using {} as allocation strategies", states);
return states;
}
@Override
public final void freeMac(String mac) {
freeMacs(Collections.singletonList(mac));
}
@Override
public final void freeMacs(List<String> macs) {
List<String> macsToRelease = filterOutUnusedMacs(macs);
if (macsToRelease.isEmpty()) {
log.warn("Trying to release MACs using empty collection as parameter.");
} else {
//we need to recalculate this on every call, since command context might change in between calls
TransactionalStrategyState strategyForMacRelease = getStrategyForMacRelease();
log.debug("Using {} as release strategy", strategyForMacRelease);
strategyForMacRelease.releaseMacsOnCommit(macsToRelease);
}
}
private List<String> filterOutUnusedMacs(List<String> macs) {
return macs.stream().filter(super::isMacInUse).collect(toList());
}
@Override
public String allocateNewMac() {
String allocatedMacAddress = super.allocateNewMac();
getStrategyForMacAllocation().forEach(e->e.releaseMacsInCaseOfRollback(Collections.singletonList(allocatedMacAddress)));
return allocatedMacAddress;
}
@Override
public final List<String> allocateMacAddresses(int numberOfAddresses) {
List<String> allocatedMacAddresses = super.allocateMacAddresses(numberOfAddresses);
getStrategyForMacAllocation().forEach(e->e.releaseMacsInCaseOfRollback(allocatedMacAddresses));
return allocatedMacAddresses;
}
@Override
public final void forceAddMac(String mac) {
super.forceAddMac(mac);
getStrategyForMacAllocation().forEach(e->e.releaseMacsInCaseOfRollback(Collections.singletonList(mac)));
}
@Override
public void forceAddMacs(List<String> macs) {
super.forceAddMacs(macs);
getStrategyForMacAllocation().forEach(e->e.releaseMacsInCaseOfRollback(macs));
}
@Override
public final boolean addMac(String mac) {
boolean added = super.addMac(mac);
if (added) {
getStrategyForMacAllocation().forEach(e->e.releaseMacsInCaseOfRollback(Collections.singletonList(mac)));
}
return added;
}
@Override
public List<String> addMacs(List<String> macs) {
List<String> notAddedMacs = super.addMacs(macs);
boolean atLeastOneAddedMac = notAddedMacs.size() < macs.size();
if (atLeastOneAddedMac) {
List<String> addedMacs = macs.stream().filter(e->!notAddedMacs.contains(e)).collect(toList());
getStrategyForMacAllocation().forEach(e->e.releaseMacsInCaseOfRollback(addedMacs));
}
return notAddedMacs;
}
private interface TransactionalStrategyState {
/**
* Macs were allocated, and they remain allocated until end of transaction. If there's "rollback of some kind",
* these macs will be released.
* @param macs allocated macs.
*/
void releaseMacsInCaseOfRollback(List<String> macs);
/**
* Macs were returned to the pool, but they remain allocated until end of transaction. Once
* "transaction of some kind" is committed, these macs will be released.
*
* @param macs macs returned to the pool.
*/
void releaseMacsOnCommit(List<String> macs);
}
private class UsingCompensationState implements TransactionalStrategyState {
private final Logger log = LoggerFactory.getLogger(UsingCompensationState.class);
private final CommandContext commandContext;
private final ReleaseMacsCompensationListener compensationListener;
public UsingCompensationState(CommandContext commandContext) {
this.commandContext = commandContext;
compensationListener = new ReleaseMacsCompensationListener();
}
@Override
public void releaseMacsInCaseOfRollback(List<String> macs) {
CompensationContext compensationContext = this.commandContext.getCompensationContext();
compensationContext.addListener(compensationListener);
compensationContext.snapshotObject(new ReleaseMacsTransientCompensation(Guid.newGuid(),
macPool.getId(),
macs));
}
@Override
public void releaseMacsOnCommit(List<String> macs) {
CompensationContext compensationContext = this.commandContext.getCompensationContext();
ReleaseMacsCompensationListener compensationListener = this.compensationListener;
log.debug("Registering macs: {} to be released in case of successful execution", macs);
compensationListener.macsToReleaseOnCommit.addAll(macs);
log.debug("Registering compensation listener {}" + compensationListener);
compensationContext.addListener(compensationListener);
}
public boolean shouldUseCompensation() {
CompensationContext compensationContext = this.commandContext.getCompensationContext();
boolean result = compensationContext != null && compensationContext.isCompensationEnabled();
log.debug("Should use compensation?: {}", result);
return result;
}
@Override
public String toString() {
return ToStringBuilder.forInstance(this).build();
}
private class ReleaseMacsCompensationListener implements CompensationContext.CompensationListener {
private final List<String> macsToReleaseOnCommit = new ArrayList<>();
@Override
public void afterCompensation() {
log.debug("Compensation occurred, clearing macs to be released after commit: {}",
macsToReleaseOnCommit);
macsToReleaseOnCommit.clear();
}
@Override
public void cleaningCompensationDataAfterSuccess() {
log.debug("Command successfully executed, releasing macs: {}" + macsToReleaseOnCommit);
macPool.freeMacs(macsToReleaseOnCommit);
}
}
}
private class UsingTxDecoratorState implements TransactionalStrategyState {
private final Logger log = LoggerFactory.getLogger(UsingTxDecoratorState.class);
@Override
public void releaseMacsInCaseOfRollback(List<String> macs) {
registerRollbackHandler(new ReturnToPoolAfterRollback(macs));
}
@Override
public void releaseMacsOnCommit(List<String> macs) {
registerRollbackHandler(new ReturnToPoolOnCommit(macs));
}
private void registerRollbackHandler(TransactionCompletionListener rollbackHandler) {
log.debug("Registering rollback handler {}", rollbackHandler);
TransactionSupport.registerRollbackHandler(rollbackHandler);
}
@Override
public String toString() {
return ToStringBuilder.forInstance(this).build();
}
private class ReturnToPoolOnCommit extends ReleaseMacsAfterEndOfTransaction {
public ReturnToPoolOnCommit(List<String> macs) {
super(macs);
}
@Override
public void onSuccess() {
log.debug("Command succeeded, releasing macs {}.", super.macs);
releaseMacs();
}
}
private class ReturnToPoolAfterRollback extends ReleaseMacsAfterEndOfTransaction {
public ReturnToPoolAfterRollback(List<String> macs) {
super(macs);
}
@Override
public void onRollback() {
log.debug("Rollback occurred, releasing macs {}.", super.macs);
releaseMacs();
}
}
private abstract class ReleaseMacsAfterEndOfTransaction extends NoOpTransactionCompletionListener {
private final List<String> macs;
public ReleaseMacsAfterEndOfTransaction(List<String> macs) {
this.macs = macs;
}
protected void releaseMacs() {
macPool.freeMacs(macs);
}
}
}
private class NontransactionalState implements TransactionalStrategyState {
private final Logger log = LoggerFactory.getLogger(NontransactionalState.class);
@Override
public void releaseMacsInCaseOfRollback(List<String> macs) {
//there won't be any.
}
@Override
public void releaseMacsOnCommit(List<String> macs) {
log.debug("Non-tx, non-compensation state, immediately releasing macs {}.", macs);
macPool.freeMacs(macs);
}
@Override
public String toString() {
return ToStringBuilder.forInstance(this).build();
}
}
}