package org.zstack.image; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusListCallBack; import org.zstack.core.cloudbus.EventCallback; import org.zstack.core.cloudbus.EventFacade; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.SimpleQuery; import org.zstack.core.db.UpdateQuery; import org.zstack.header.Component; import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.image.*; import org.zstack.header.message.MessageReply; import org.zstack.header.storage.backup.BackupStorageCanonicalEvents; import org.zstack.header.storage.backup.BackupStorageCanonicalEvents.BackupStorageStatusChangedData; import org.zstack.header.storage.backup.BackupStorageStatus; import org.zstack.header.storage.primary.*; import org.zstack.utils.CollectionUtils; import org.zstack.utils.StringDSL; import org.zstack.utils.Utils; import org.zstack.utils.function.Function; import org.zstack.utils.logging.CLogger; import javax.persistence.Tuple; import javax.persistence.TypedQuery; import java.io.File; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * Created by xing5 on 2016/5/6. */ public class ImageUpgradeExtension implements Component { private static final CLogger logger = Utils.getLogger(ImageUpgradeExtension.class); @Autowired private DatabaseFacade dbf; @Autowired private EventFacade evtf; @Autowired private CloudBus bus; @Override public boolean start() { if (ImageGlobalProperty.SYNC_IMAGE_ACTUAL_SIZE_ON_START) { syncImageActualSize(); } if (ImageGlobalProperty.FIX_IMAGE_CACHE_UUID) { fixImageCacheUuid(); } return true; } @Transactional private void fixImageCacheUuid() { String sql = "select c, pri.type from ImageCacheVO c, PrimaryStorageVO pri where c.primaryStorageUuid = pri.uuid" + " and c.imageUuid is null"; TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class); List<Tuple> ts = q.getResultList(); for (Tuple t : ts) { ImageCacheVO c = t.get(0, ImageCacheVO.class); String psType = t.get(1, String.class); String imgUuid; if ("Ceph".equals(psType)) { imgUuid = c.getInstallUrl().split("@")[1]; } else if ("NFS".equals(psType) || "SharedMountPoint".equals(psType)) { imgUuid = new File(c.getInstallUrl()).getName().split("\\.")[0]; } else if ("LocalStorage".equals(psType)) { String[] pair = c.getInstallUrl().split(";"); imgUuid = new File(pair[0]).getName().split("\\.")[0]; } else { throw new CloudRuntimeException(String.format("unknown primary storage type[%s] for the ImageCacheVO[id:%s]", psType, c.getId())); } if (!StringDSL.isZstackUuid(imgUuid)) { throw new CloudRuntimeException(String.format("the image UUID[%s] parsed from the URL[%s] of the ImageCacheVO[id:%s] " + "on the primary storage[type:%s] looks no correct", imgUuid, c.getInstallUrl(), c.getId(), psType)); } c.setImageUuid(imgUuid); dbf.getEntityManager().merge(c); } } private void syncImageActualSize() { evtf.on(BackupStorageCanonicalEvents.BACKUP_STORAGE_STATUS_CHANGED, new EventCallback() { @Transactional(readOnly = true) private List<ImageInventory> getImagesForSync(String bsUuid) { String sql = "select img from ImageVO img, ImageBackupStorageRefVO ref where" + " img.uuid = ref.imageUuid and ref.backupStorageUuid = :bsUuid"; TypedQuery<ImageVO> q = dbf.getEntityManager().createQuery(sql, ImageVO.class); q.setParameter("bsUuid", bsUuid); List<ImageVO> vos = q.getResultList(); return ImageInventory.valueOf(vos); } @Override public void run(Map tokens, Object data) { if (!evtf.isFromThisManagementNode(tokens)) { return; } final BackupStorageStatusChangedData d = (BackupStorageStatusChangedData) data; if (!BackupStorageStatus.Connected.toString().equals(d.getNewStatus())) { return; } final List<ImageInventory> imgs = getImagesForSync(d.getBackupStorageUuid()); if (imgs.isEmpty()) { return; } List<SyncImageSizeMsg> msgs = CollectionUtils.transformToList(imgs, new Function<SyncImageSizeMsg, ImageInventory>() { @Override public SyncImageSizeMsg call(ImageInventory arg) { SyncImageSizeMsg msg = new SyncImageSizeMsg(); msg.setBackupStorageUuid(d.getBackupStorageUuid()); msg.setImageUuid(arg.getUuid()); bus.makeTargetServiceIdByResourceUuid(msg, ImageConstant.SERVICE_ID, arg.getUuid()); return msg; } }); bus.send(msgs, new CloudBusListCallBack(null) { @Override public void run(List<MessageReply> replies) { Map<String, Long> res = new HashMap<>(); for (MessageReply r : replies) { ImageInventory image = imgs.get(replies.indexOf(r)); if (!r.isSuccess()) { logger.warn(String.format("failed to sync image [%s] size: %s", image.getUuid(), r.getError().getDetails())); continue; } SyncImageSizeReply reply = r.castReply(); if (!res.containsKey(image.getUuid())) { res.put(image.getUuid(), reply.getActualSize()); } } for (Map.Entry<String, Long> entry: res.entrySet()) { UpdateQuery.New(ImageCacheVO.class) .set(ImageCacheVO_.size, entry.getValue()) .condAnd(ImageCacheVO_.imageUuid, SimpleQuery.Op.EQ, entry.getKey()) .update(); } // Ask primary storage service to recalculate the capacities final List<RecalculatePrimaryStorageCapacityMsg> rmsgs = dbf.listAll(PrimaryStorageVO.class).stream() .map((psvo) -> { RecalculatePrimaryStorageCapacityMsg rmsg = new RecalculatePrimaryStorageCapacityMsg(); rmsg.setPrimaryStorageUuid(psvo.getUuid()); bus.makeLocalServiceId(rmsg, PrimaryStorageConstant.SERVICE_ID); return rmsg; }).collect(Collectors.toList()); bus.send(rmsgs); } }); } }); } @Override public boolean stop() { return true; } }