package org.zstack.storage.backup;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.header.errorcode.OperationFailureException;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.storage.backup.BackupStorageCapacity;
import org.zstack.header.storage.backup.BackupStorageCapacityUpdaterRunnable;
import org.zstack.header.storage.backup.BackupStorageVO;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
import static org.zstack.core.Platform.operr;
import javax.persistence.LockModeType;
/**
* Created by xing5 on 2016/4/28.
*/
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class BackupStorageCapacityUpdater {
private static CLogger logger = Utils.getLogger(BackupStorageCapacityUpdater.class);
@Autowired
private DatabaseFacade dbf;
@Autowired
private CloudBus bus;
@Autowired
private ErrorFacade errf;
private String backupStorageUuid;
private BackupStorageVO capacityVO;
private BackupStorageVO originalCopy;
private long totalForLog;
private long availForLog;
public BackupStorageCapacityUpdater(String backupStorageUuid) {
this.backupStorageUuid = backupStorageUuid;
}
private void logCapacityChange() {
if (logger.isTraceEnabled()) {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
int index = 0;
String fileName = BackupStorageCapacityUpdater.class.getSimpleName() + ".java";
for (int i=0; i<stackTraceElements.length; i++) {
if (fileName.equals(stackTraceElements[i].getFileName())) {
index = i;
}
}
StackTraceElement caller = stackTraceElements[index+1];
logger.trace(String.format("[Backup Storage Capacity] %s:%s:%s changed the capacity of the backup storage[uuid:%s] as:\n" +
"total: %s --> %s\n" +
"available: %s --> %s\n" , caller.getFileName(), caller.getMethodName(), caller.getLineNumber(), capacityVO.getUuid(),
totalForLog, capacityVO.getTotalCapacity(),
availForLog, capacityVO.getAvailableCapacity()));
}
}
private boolean lockCapacity() {
if (backupStorageUuid != null) {
capacityVO = dbf.getEntityManager().find(BackupStorageVO.class, backupStorageUuid, LockModeType.PESSIMISTIC_WRITE);
}
if (capacityVO != null) {
totalForLog = capacityVO.getTotalCapacity();
availForLog = capacityVO.getAvailableCapacity();
originalCopy = new BackupStorageVO();
originalCopy.setAvailableCapacity(capacityVO.getAvailableCapacity());
originalCopy.setTotalCapacity(capacityVO.getTotalCapacity());
}
return capacityVO != null;
}
private void checkResize() {
if (originalCopy != null && capacityVO != null && originalCopy.getTotalCapacity() != 0 && originalCopy.getTotalCapacity() != capacityVO.getTotalCapacity()) {
logger.debug(String.format("the capacity of backup storage[uuid:%s] changed from %s to %s, this indicates the backup storage is re-sized." +
" We need to recalculate its capacity", capacityVO.getUuid(), originalCopy.getTotalCapacity(), capacityVO.getTotalCapacity()));
// primary storage re-sized
}
}
private void merge() {
capacityVO = dbf.getEntityManager().merge(capacityVO);
logCapacityChange();
}
private void logDeletedPrimaryStorage() {
logger.warn(String.format("[Backup Storage Capacity] unable to update capacity for the backup storage[uuid:%s]. It may have been deleted, cannot find it in database",
backupStorageUuid));
}
@Transactional
private boolean _increaseAvailableCapacity(long size) {
if (!lockCapacity()) {
logDeletedPrimaryStorage();
return false;
}
long n = capacityVO.getAvailableCapacity() + size;
if (n > capacityVO.getTotalCapacity()) {
throw new CloudRuntimeException(String.format("invalid primary storage[uuid:%s] capacity, available capacity[%s] > total capacity[%s]",
capacityVO.getUuid(), n, capacityVO.getTotalCapacity()));
}
capacityVO.setAvailableCapacity(n);
merge();
return true;
}
public boolean reserveCapacity(long size) {
return _reserveCapacity(size, true);
}
public boolean reserveCapacity(long size, boolean exceptionOnFailure) {
return _reserveCapacity(size, exceptionOnFailure);
}
@Transactional
private boolean _reserveCapacity(long size, boolean exceptionOnFailure) {
if (!lockCapacity()) {
logDeletedPrimaryStorage();
return false;
}
if (capacityVO.getAvailableCapacity() < size) {
if (!exceptionOnFailure) {
return false;
} else {
throw new OperationFailureException(operr("cannot reserve %s on the backup storage[uuid:%s], it only has %s available",
size, backupStorageUuid, capacityVO.getAvailableCapacity()));
}
}
return _decreaseAvailableCapacity(size);
}
public boolean increaseAvailableCapacity(long size) {
boolean ret = _increaseAvailableCapacity(size);
checkResize();
return ret;
}
@Transactional
private boolean _decreaseAvailableCapacity(long size) {
if (!lockCapacity()) {
logDeletedPrimaryStorage();
return false;
}
capacityVO.setAvailableCapacity(capacityVO.getAvailableCapacity() - size);
merge();
return true;
}
public boolean decreaseAvailableCapacity(long size) {
boolean ret = _decreaseAvailableCapacity(size);
checkResize();
return ret;
}
@Transactional
private boolean _update(Long total, Long avail) {
if (!lockCapacity()) {
logDeletedPrimaryStorage();
return false;
}
if (total != null) {
capacityVO.setTotalCapacity(total);
}
if (avail != null) {
capacityVO.setAvailableCapacity(avail);
}
merge();
return true;
}
public boolean update(Long total, Long avail) {
boolean ret = _update(total, avail);
checkResize();
return ret;
}
@Transactional
private boolean _run(BackupStorageCapacityUpdaterRunnable runnable) {
if (!lockCapacity()) {
logDeletedPrimaryStorage();
return false;
}
BackupStorageCapacity cap = new BackupStorageCapacity();
cap.setUuid(capacityVO.getUuid());
cap.setAvailableCapacity(capacityVO.getAvailableCapacity());
cap.setTotalCapacity(capacityVO.getTotalCapacity());
cap = runnable.call(cap);
if (cap != null) {
capacityVO.setTotalCapacity(cap.getTotalCapacity());
capacityVO.setAvailableCapacity(cap.getAvailableCapacity());
merge();
return true;
}
return false;
}
public boolean run(BackupStorageCapacityUpdaterRunnable runnable) {
boolean ret = _run(runnable);
checkResize();
return ret;
}
}