package org.zstack.storage.primary; 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.db.DeadlockAutoRestart; import org.zstack.core.errorcode.ErrorFacade; import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.storage.primary.PrimaryStorageCapacityUpdaterRunnable; import org.zstack.header.storage.primary.PrimaryStorageCapacityVO; import org.zstack.header.storage.primary.PrimaryStorageConstant; import org.zstack.header.storage.primary.RecalculatePrimaryStorageCapacityMsg; import org.zstack.utils.Utils; import org.zstack.utils.logging.CLogger; import static org.zstack.core.Platform.operr; import javax.persistence.LockModeType; import javax.persistence.TypedQuery; import java.util.List; /** * Created by frank on 10/19/2015. */ @Configurable(preConstruction = true, autowire = Autowire.BY_TYPE) public class PrimaryStorageCapacityUpdater { private static CLogger logger = Utils.getLogger(PrimaryStorageCapacityUpdater.class); @Autowired private DatabaseFacade dbf; @Autowired private CloudBus bus; @Autowired private ErrorFacade errf; private String primaryStorageUuid; private TypedQuery<PrimaryStorageCapacityVO> query; private PrimaryStorageCapacityVO capacityVO; private PrimaryStorageCapacityVO originalCopy; private long totalForLog; private long availForLog; private long totalPhysicalForLog; private long availPhysicalForLog; public PrimaryStorageCapacityUpdater(String primaryStorageUuid) { this.primaryStorageUuid = primaryStorageUuid; } public PrimaryStorageCapacityUpdater(TypedQuery<PrimaryStorageCapacityVO> query) { this.query = query; } private void logDeletedPrimaryStorage() { logger.warn(String.format("[Primary Storage Capacity] unable to update capacity for the primary storage[uuid:%s]." + " It may have been deleted, cannot find it in database", primaryStorageUuid)); } private void logCapacityChange() { if (logger.isTraceEnabled()) { StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); int index = 0; String fileName = PrimaryStorageCapacityUpdater.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("[Primary Storage Capacity] %s:%s:%s changed the capacity of the primary storage[uuid:%s] as:\n" + "total: %s --> %s\n" + "available: %s --> %s\n" + "physical total: %s --> %s\n" + "physical available: %s --> %s\n", caller.getFileName(), caller.getMethodName(), caller.getLineNumber(), capacityVO.getUuid(), totalForLog, capacityVO.getTotalCapacity(), availForLog, capacityVO.getAvailableCapacity(), totalPhysicalForLog, capacityVO.getTotalPhysicalCapacity(), availPhysicalForLog, capacityVO.getAvailablePhysicalCapacity())); } } private boolean lockCapacity() { if (primaryStorageUuid != null) { capacityVO = dbf.getEntityManager().find(PrimaryStorageCapacityVO.class, primaryStorageUuid, LockModeType.PESSIMISTIC_WRITE); } else if (query != null) { query.setLockMode(LockModeType.PESSIMISTIC_WRITE); List<PrimaryStorageCapacityVO> caps = query.getResultList(); capacityVO = caps.isEmpty() ? null : caps.get(0); } if (capacityVO != null) { totalForLog = capacityVO.getTotalCapacity(); availForLog = capacityVO.getAvailableCapacity(); totalPhysicalForLog = capacityVO.getTotalPhysicalCapacity(); availPhysicalForLog = capacityVO.getAvailablePhysicalCapacity(); originalCopy = new PrimaryStorageCapacityVO(); originalCopy.setAvailableCapacity(capacityVO.getAvailableCapacity()); originalCopy.setTotalCapacity(capacityVO.getTotalCapacity()); originalCopy.setAvailablePhysicalCapacity(capacityVO.getAvailablePhysicalCapacity()); originalCopy.setTotalPhysicalCapacity(capacityVO.getTotalPhysicalCapacity()); originalCopy.setSystemUsedCapacity(capacityVO.getSystemUsedCapacity()); } return capacityVO != null; } private boolean isResized() { return originalCopy != null && capacityVO != null && originalCopy.getTotalPhysicalCapacity() != 0 && originalCopy.getTotalPhysicalCapacity() != capacityVO.getTotalPhysicalCapacity(); } private void checkResize() { if (isResized()) { logger.debug(String.format("the physical capacity of primary storage[uuid:%s] changed from %s to %s," + " this indicates the primary storage is re-sized." + " We need to recalculate its capacity", capacityVO.getUuid(), originalCopy.getTotalPhysicalCapacity(), capacityVO.getTotalPhysicalCapacity())); // primary storage re-sized RecalculatePrimaryStorageCapacityMsg msg = new RecalculatePrimaryStorageCapacityMsg(); msg.setPrimaryStorageUuid(capacityVO.getUuid()); bus.makeLocalServiceId(msg, PrimaryStorageConstant.SERVICE_ID); bus.send(msg); } } private void merge() { if (isResized()) { capacityVO.setTotalCapacity(capacityVO.getTotalPhysicalCapacity()); } capacityVO = dbf.getEntityManager().merge(capacityVO); logCapacityChange(); } @Transactional private boolean _updateAvailablePhysicalCapacity(long avail) { if (!lockCapacity()) { logDeletedPrimaryStorage(); return false; } capacityVO.setAvailablePhysicalCapacity(avail); merge(); return true; } @DeadlockAutoRestart public boolean updateAvailablePhysicalCapacity(long avail) { boolean ret = _updateAvailablePhysicalCapacity(avail); checkResize(); return ret; } @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; } @DeadlockAutoRestart 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; } @DeadlockAutoRestart public boolean decreaseAvailableCapacity(long size) { boolean ret = _decreaseAvailableCapacity(size); checkResize(); return ret; } @Transactional private boolean _update(Long total, Long avail, Long physicalTotal, Long physicalAvail) { if (!lockCapacity()) { logDeletedPrimaryStorage(); return false; } if (capacityVO.getSystemUsedCapacity() == null && physicalTotal != null && physicalAvail != null) { capacityVO.setSystemUsedCapacity(physicalTotal - physicalAvail); } if (total != null) { capacityVO.setTotalCapacity(total); } if (avail != null) { capacityVO.setAvailableCapacity(avail); } if (physicalTotal != null) { capacityVO.setTotalPhysicalCapacity(physicalTotal); } if (physicalAvail != null) { capacityVO.setAvailablePhysicalCapacity(physicalAvail); } merge(); return true; } @DeadlockAutoRestart public boolean update(Long total, Long avail, Long physicalTotal, Long physicalAvail) { boolean ret = _update(total, avail, physicalTotal, physicalAvail); checkResize(); return ret; } @Transactional private boolean _run(PrimaryStorageCapacityUpdaterRunnable runnable) { if (!lockCapacity()) { logDeletedPrimaryStorage(); return false; } PrimaryStorageCapacityVO cap = runnable.call(capacityVO); if (cap != null) { capacityVO = cap; merge(); return true; } return false; } @DeadlockAutoRestart public boolean run(PrimaryStorageCapacityUpdaterRunnable runnable) { boolean ret = _run(runnable); checkResize(); return ret; } public boolean reserve(long size) { return reserve(size, true); } @Transactional private boolean _reserve(long size, boolean exceptionOnFailure) { if (!lockCapacity()) { logDeletedPrimaryStorage(); return false; } if (capacityVO.getAvailableCapacity() < size) { if (exceptionOnFailure) { throw new OperationFailureException(operr("cannot reserve %s bytes on the primary storage[uuid:%s]," + " it's short of available capacity", size, capacityVO.getUuid())); } else { return false; } } capacityVO.setAvailableCapacity(capacityVO.getAvailableCapacity() - size); merge(); return true; } @DeadlockAutoRestart public boolean reserve(long size, boolean exceptionOnFailure) { return _reserve(size, exceptionOnFailure); } }