package org.zstack.storage.backup; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.zstack.core.Platform; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusCallBack; import org.zstack.core.cloudbus.MessageSafe; import org.zstack.core.cloudbus.ResourceDestinationMaker; import org.zstack.core.componentloader.PluginRegistry; import org.zstack.core.config.GlobalConfigException; import org.zstack.core.config.GlobalConfigValidatorExtensionPoint; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.DbEntityLister; import org.zstack.core.db.SimpleQuery; import org.zstack.core.errorcode.ErrorFacade; import org.zstack.core.thread.AsyncThread; import org.zstack.header.AbstractService; import org.zstack.header.errorcode.SysErrors; import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.managementnode.ManagementNodeChangeListener; import org.zstack.header.managementnode.ManagementNodeReadyExtensionPoint; import org.zstack.header.message.APIMessage; import org.zstack.header.message.Message; import org.zstack.header.message.MessageReply; import org.zstack.header.storage.backup.*; import org.zstack.search.GetQuery; import org.zstack.search.SearchQuery; import org.zstack.tag.TagManager; import org.zstack.utils.CollectionUtils; import org.zstack.utils.ObjectUtils; import org.zstack.utils.SizeUtils; import org.zstack.utils.Utils; import org.zstack.utils.function.ForEachFunction; import org.zstack.utils.logging.CLogger; import static org.zstack.core.Platform.operr; import javax.persistence.LockModeType; import javax.persistence.Tuple; import javax.persistence.TypedQuery; import java.util.*; import java.util.concurrent.Callable; public class BackupStorageManagerImpl extends AbstractService implements BackupStorageManager, ManagementNodeChangeListener, ManagementNodeReadyExtensionPoint { private static final CLogger logger = Utils.getLogger(BackupStorageManager.class); @Autowired private CloudBus bus; @Autowired private PluginRegistry pluginRgty; @Autowired private DatabaseFacade dbf; @Autowired private DbEntityLister dl; @Autowired private ErrorFacade errf; @Autowired private ResourceDestinationMaker destMaker; @Autowired private TagManager tagMgr; private Map<String, BackupStorageFactory> backupStorageFactories = Collections.synchronizedMap(new HashMap<String, BackupStorageFactory>()); private static final Set<Class> allowedMessageAfterDeletion = new HashSet<Class>(); private Map<String, BackupStorageAllocatorStrategyFactory> allocatorStrategyFactories = new HashMap<String, BackupStorageAllocatorStrategyFactory>(); static { allowedMessageAfterDeletion.add(BackupStorageDeletionMsg.class); } private void handleApiMessage(APIMessage msg) { try { if (msg instanceof APIAddBackupStorageMsg) { handle((APIAddBackupStorageMsg) msg); } else if (msg instanceof APIListBackupStorageMsg) { handle((APIListBackupStorageMsg) msg); } else if (msg instanceof APISearchBackupStorageMsg) { handle((APISearchBackupStorageMsg) msg); } else if (msg instanceof APIGetBackupStorageMsg) { handle((APIGetBackupStorageMsg) msg); } else if (msg instanceof APIGetBackupStorageTypesMsg) { handle((APIGetBackupStorageTypesMsg) msg); } else if (msg instanceof APIGetBackupStorageCapacityMsg) { handle((APIGetBackupStorageCapacityMsg) msg); } else if (msg instanceof BackupStorageMessage) { passThrough((BackupStorageMessage) msg); } else { bus.dealWithUnknownMessage(msg); } } catch (Exception e) { bus.logExceptionWithMessageDump(msg, e); bus.replyErrorByMessageType(msg, e); } } private void handle(final APIGetBackupStorageCapacityMsg msg) { APIGetBackupStorageCapacityReply reply = new APIGetBackupStorageCapacityReply(); Tuple ret = new Callable<Tuple>() { @Override @Transactional(readOnly = true) public Tuple call() { if (msg.getBackupStorageUuids() != null && !msg.getBackupStorageUuids().isEmpty()) { String sql = "select sum(bs.totalCapacity), sum(bs.availableCapacity) from BackupStorageVO bs where bs.uuid in (:bsUuids)"; TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class); q.setParameter("bsUuids", msg.getBackupStorageUuids()); return q.getSingleResult(); } else if (msg.getZoneUuids() != null && !msg.getZoneUuids().isEmpty()) { String sql = "select sum(bs.totalCapacity), sum(bs.availableCapacity) from BackupStorageVO bs, BackupStorageZoneRefVO ref where ref.backupStorageUuid = bs.uuid and ref.zoneUuid in (:zoneUuids)"; TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class); q.setParameter("zoneUuids", msg.getZoneUuids()); return q.getSingleResult(); } throw new CloudRuntimeException("should not be here"); } }.call(); Long total = ret.get(0, Long.class); Long avail = ret.get(1, Long.class); reply.setTotalCapacity(total == null ? 0 : total); reply.setAvailableCapacity(avail == null ? 0 : avail); bus.reply(msg, reply); } private void handle(APIGetBackupStorageTypesMsg msg) { List<String> types = new ArrayList<String>(); APIGetBackupStorageTypesReply reply = new APIGetBackupStorageTypesReply(); types.addAll(BackupStorageType.getAllTypeNames()); reply.setBackupStorageTypes(types); bus.reply(msg, reply); } private void handle(APIGetBackupStorageMsg msg) { GetQuery q = new GetQuery(); String res = q.getAsString(msg, BackupStorageInventory.class); APIGetBackupStorageReply reply = new APIGetBackupStorageReply(); reply.setInventory(res); bus.reply(msg, reply); } private void handle(APISearchBackupStorageMsg msg) { SearchQuery<BackupStorageInventory> sq = SearchQuery.create(msg, BackupStorageInventory.class); String content = sq.listAsString(); APISearchBackupStorageReply reply = new APISearchBackupStorageReply(); reply.setContent(content); bus.reply(msg, reply); } private void handle(APIListBackupStorageMsg msg) { List<BackupStorageVO> vos = dl.listByApiMessage(msg, BackupStorageVO.class); List<BackupStorageInventory> invs = BackupStorageInventory.valueOf(vos); APIListBackupStorageReply reply = new APIListBackupStorageReply(); reply.setInventories(invs); bus.reply(msg, reply); } private void passThrough(BackupStorageMessage pmsg) { BackupStorageVO vo = dbf.findByUuid(pmsg.getBackupStorageUuid(), BackupStorageVO.class); if (vo == null && allowedMessageAfterDeletion.contains(pmsg.getClass())) { BackupStorageEO eo = dbf.findByUuid(pmsg.getBackupStorageUuid(), BackupStorageEO.class); vo = ObjectUtils.newAndCopy(eo, BackupStorageVO.class); } Message msg = (Message) pmsg; if (vo == null) { String err = String.format("Cannot find backup storage[uuid:%s], it may have been deleted", pmsg.getBackupStorageUuid()); bus.replyErrorByMessageType(msg, err); return; } BackupStorageFactory factory = getBackupStorageFactory(BackupStorageType.valueOf(vo.getType())); BackupStorage ss = factory.getBackupStorage(vo); ss.handleMessage(msg); } private void handle(final APIAddBackupStorageMsg msg) { BackupStorageType type = BackupStorageType.valueOf(msg.getType()); final BackupStorageFactory factory = getBackupStorageFactory(type); BackupStorageVO vo = new BackupStorageVO(); if (msg.getResourceUuid() != null) { vo.setUuid(msg.getResourceUuid()); } else { vo.setUuid(Platform.getUuid()); } vo.setUrl(msg.getUrl()); vo.setType(type.toString()); vo.setName(msg.getName()); vo.setDescription(vo.getDescription()); vo.setState(BackupStorageState.Enabled); vo.setStatus(BackupStorageStatus.Connecting); final BackupStorageInventory inv = factory.createBackupStorage(vo, msg); AddBackupStorageStruct addBackupStoragestruct = new AddBackupStorageStruct(); if(msg.isImportImages()) { addBackupStoragestruct.setImportImages(true); } addBackupStoragestruct.setBackupStorageInventory(inv); addBackupStoragestruct.setType(vo.getType()); tagMgr.createTagsFromAPICreateMessage(msg, inv.getUuid(), BackupStorageVO.class.getSimpleName()); final APIAddBackupStorageEvent evt = new APIAddBackupStorageEvent(msg.getId()); ConnectBackupStorageMsg cmsg = new ConnectBackupStorageMsg(); cmsg.setNewAdd(true); cmsg.setBackupStorageUuid(inv.getUuid()); bus.makeTargetServiceIdByResourceUuid(cmsg, BackupStorageConstant.SERVICE_ID, vo.getUuid()); bus.send(cmsg, new CloudBusCallBack(msg) { @Override public void run(MessageReply reply) { if (reply.isSuccess()) { evt.setInventory(factory.reload(inv.getUuid())); bus.publish(evt); CollectionUtils.safeForEach(pluginRgty.getExtensionList(AddBackupStorageExtensionPoint.class), new ForEachFunction<AddBackupStorageExtensionPoint>() { @Override public void run(AddBackupStorageExtensionPoint ext) { ext.afterAddBackupStorage(addBackupStoragestruct); } }); } else { dbf.removeByPrimaryKey(inv.getUuid(), BackupStorageVO.class); evt.setError(errf.instantiateErrorCode(SysErrors.CREATE_RESOURCE_ERROR, reply.getError())); bus.publish(evt); } } }); } @Override @MessageSafe public void handleMessage(Message msg) { if (msg instanceof APIMessage) { handleApiMessage((APIMessage) msg); } else { handleLocalMessage(msg); } } private void handleLocalMessage(Message msg) { if (msg instanceof BackupStorageMessage) { passThrough((BackupStorageMessage) msg); } else if (msg instanceof AllocateBackupStorageMsg) { handle((AllocateBackupStorageMsg) msg); } else { bus.dealWithUnknownMessage(msg); } } @Transactional private boolean reserve(String bsUuid, long size) { BackupStorageVO vo = dbf.getEntityManager().find(BackupStorageVO.class, bsUuid, LockModeType.PESSIMISTIC_WRITE); if (vo == null) { logger.warn(String.format("reservation failure, cannot find backup storage[uuid:%s]", bsUuid)); return false; } if (vo.getAvailableCapacity() < size) { logger.warn(String.format("reservation failure, cannot reserve capacity[%s bytes] on backup storage[uuid:%s]", size, bsUuid)); return false; } long avail = vo.getAvailableCapacity() - size; vo.setAvailableCapacity(avail); dbf.getEntityManager().merge(vo); logger.debug(String.format("reserve %s bytes on backup storage[uuid:%s, total capacity:%s, available capacity:%s]", size, bsUuid, vo.getTotalCapacity(), avail)); return true; } private void handle(AllocateBackupStorageMsg msg) { String allocatorStrategy = msg.getAllocatorStrategy() == null ? BackupStorageConstant.DEFAULT_ALLOCATOR_STRATEGY : msg.getAllocatorStrategy(); BackupStorageAllocatorStrategyFactory factory = getAllocatorFactory(allocatorStrategy); BackupStorageAllocatorStrategy strategy = factory.getAllocatorStrategy(); BackupStorageAllocationSpec spec = new BackupStorageAllocationSpec(); spec.setAllocationMessage(msg); spec.setRequiredBackupStorageUuid(msg.getBackupStorageUuid()); spec.setSize(msg.getSize()); spec.setRequiredZoneUuid(msg.getRequiredZoneUuid()); AllocateBackupStorageReply reply = new AllocateBackupStorageReply(); try { List<BackupStorageInventory> invs = strategy.allocateAllCandidates(spec); Iterator<BackupStorageInventory> it = invs.iterator(); BackupStorageInventory target = null; while (it.hasNext()) { BackupStorageInventory inv = it.next(); if (reserve(inv.getUuid(), msg.getSize())) { target = inv; break; } logger.debug(String.format("concurrent reservation on backup storage[uuid:%s], try next", inv.getUuid())); } if (target == null) { reply.setError(operr("capacity reservation on all backup storage failed")); } else { reply.setInventory(target); } } catch (BackupStorageException e) { reply.setError(e.getError()); } bus.reply(msg, reply); } @Override public String getId() { return bus.makeLocalServiceId(BackupStorageConstant.SERVICE_ID); } @Override public boolean start() { populateBackupStorageFactory(); installValidatorToGlobalConfig(); return true; } private void installValidatorToGlobalConfig() { BackupStorageGlobalConfig.RESERVED_CAPACITY.installValidateExtension(new GlobalConfigValidatorExtensionPoint() { @Override public void validateGlobalConfig(String category, String name, String oldValue, String newValue) throws GlobalConfigException { if (!SizeUtils.isSizeString(newValue)) { throw new GlobalConfigException(String.format("%s is not a size string; a size string consists of a number ending with suffix B/K/M/G/T or without suffix; for example, 512M, 1G", newValue)); } } }); } @Override public boolean stop() { return true; } private void populateBackupStorageFactory() { for (BackupStorageFactory factory : pluginRgty.getExtensionList(BackupStorageFactory.class)) { BackupStorageFactory old = backupStorageFactories.get(factory.getBackupStorageType().toString()); if (old != null) { throw new CloudRuntimeException(String.format("duplicate BackupStorageFactory[%s, %s] for type[%s]", factory.getClass().getName(), old.getClass().getName(), old.getBackupStorageType())); } backupStorageFactories.put(factory.getBackupStorageType().toString(), factory); } for (BackupStorageAllocatorStrategyFactory factory : pluginRgty.getExtensionList(BackupStorageAllocatorStrategyFactory.class)) { BackupStorageAllocatorStrategyFactory old = allocatorStrategyFactories.get(factory.getType().toString()); if (old != null) { throw new CloudRuntimeException(String.format("duplicate BackupStorageAllocatorStrategyFactory[%s, %s] for type[%s]", old.getClass().getName(), factory.getClass().getName(), factory.getType())); } allocatorStrategyFactories.put(factory.getType().toString(), factory); } } @Override public BackupStorageFactory getBackupStorageFactory(BackupStorageType type) { BackupStorageFactory factory = backupStorageFactories.get(type.toString()); if (factory == null) { throw new CloudRuntimeException(String.format("No BackupStorageFactory for type: %s found", type)); } return factory; } @Override public BackupStorageAllocatorStrategyFactory getAllocatorFactory(String type) { BackupStorageAllocatorStrategyFactory factory = allocatorStrategyFactories.get(type); if (factory == null) { throw new CloudRuntimeException(String.format("no BackupStorageAllocatorStrategyFactory[%s] found", type)); } return factory; } private List<String> getBackupStorageManagedByUs() { List<String> ret = new ArrayList<String>(); SimpleQuery<BackupStorageVO> q = dbf.createQuery(BackupStorageVO.class); q.select(BackupStorageVO_.uuid); List<String> uuids = q.listValue(); for (String uuid : uuids) { if (destMaker.isManagedByUs(uuid)) { ret.add(uuid); } } return ret; } private void loadBackupStorage() { List<String> uuids = getBackupStorageManagedByUs(); if (uuids.isEmpty()) { return; } List<ConnectBackupStorageMsg> msgs = new ArrayList<ConnectBackupStorageMsg>(); for (String uuid : uuids) { ConnectBackupStorageMsg msg = new ConnectBackupStorageMsg(); msg.setBackupStorageUuid(uuid); bus.makeTargetServiceIdByResourceUuid(msg, BackupStorageConstant.SERVICE_ID, uuid); msgs.add(msg); } bus.send(msgs); } @Override public void nodeJoin(String nodeId) { } @Override public void nodeLeft(String nodeId) { logger.debug(String.format("management node[uuid:%s] left, node[uuid:%s] starts taking over backup storage...", nodeId, Platform.getManagementServerId())); loadBackupStorage(); } @Override public void iAmDead(String nodeId) { } @Override public void iJoin(String nodeId) { } @Override @AsyncThread public void managementNodeReady() { logger.debug(String.format("management node[uuid:%s] joins, starts load backup storage...", Platform.getManagementServerId())); loadBackupStorage(); } }