package org.zstack.image; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.zstack.core.cascade.AbstractAsyncCascadeExtension; import org.zstack.core.cascade.CascadeAction; import org.zstack.core.cascade.CascadeConstant; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusListCallBack; import org.zstack.core.db.DatabaseFacade; import org.zstack.header.core.Completion; import org.zstack.header.identity.AccountInventory; import org.zstack.header.identity.AccountVO; import org.zstack.header.image.*; import org.zstack.header.image.ImageDeletionPolicyManager.ImageDeletionPolicy; import org.zstack.header.message.MessageReply; import org.zstack.header.storage.backup.BackupStorageInventory; import org.zstack.header.storage.backup.BackupStorageVO; import org.zstack.utils.CollectionUtils; 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.util.*; import java.util.concurrent.Callable; /** */ public class ImageCascadeExtension extends AbstractAsyncCascadeExtension { private static final CLogger logger = Utils.getLogger(ImageCascadeExtension.class); @Autowired private DatabaseFacade dbf; @Autowired private CloudBus bus; private static final String NAME = ImageVO.class.getSimpleName(); @Override public void asyncCascade(CascadeAction action, Completion completion) { if (action.isActionCode(CascadeConstant.DELETION_CHECK_CODE)) { handleDeletionCheck(action, completion); } else if (action.isActionCode(CascadeConstant.DELETION_DELETE_CODE, CascadeConstant.DELETION_FORCE_DELETE_CODE)) { handleDeletion(action, completion); } else if (action.isActionCode(CascadeConstant.DELETION_CLEANUP_CODE)) { handleDeletionCleanup(action, completion); } else { completion.success(); } } private void handleDeletionCleanup(CascadeAction action, Completion completion) { cleanupImageEO(); completion.success(); } private void cleanupImageEO() { String sql = "select i.uuid from ImageEO i where i.deleted is not null and i.uuid not in (select vm.imageUuid from VmInstanceVO vm where vm.imageUuid is not null)"; dbf.hardDeleteCollectionSelectedBySQL(sql, ImageVO.class); } private void handleDeletion(final CascadeAction action, final Completion completion) { final List<ImageDeletionStruct> structs = imageFromAction(action); if (structs == null) { completion.success(); return; } List<ImageDeletionMsg> msgs = CollectionUtils.transformToList(structs, new Function<ImageDeletionMsg, ImageDeletionStruct>() { @Override public ImageDeletionMsg call(ImageDeletionStruct arg) { ImageDeletionMsg msg = new ImageDeletionMsg(); msg.setImageUuid(arg.getImage().getUuid()); if (!arg.getDeleteAll()) { msg.setBackupStorageUuids(arg.getBackupStorageUuids()); } ImageDeletionPolicy deletionPolicy = deletionPolicyFromAction(action); msg.setDeletionPolicy(deletionPolicy == null ? null : deletionPolicy.toString()); bus.makeTargetServiceIdByResourceUuid(msg, ImageConstant.SERVICE_ID, arg.getImage().getUuid()); msg.setForceDelete(action.isActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE)); return msg; } }); bus.send(msgs, new CloudBusListCallBack(completion) { @Override public void run(List<MessageReply> replies) { if (!action.isActionCode(CascadeConstant.DELETION_FORCE_DELETE_CODE)) { for (MessageReply r : replies) { if (!r.isSuccess()) { completion.fail(r.getError()); return; } } } completion.success(); } }); } private void handleDeletionCheck(CascadeAction action, Completion completion) { completion.success(); } @Override public List<String> getEdgeNames() { return Arrays.asList(BackupStorageVO.class.getSimpleName(), AccountVO.class.getSimpleName()); } @Override public String getCascadeResourceName() { return NAME; } @Transactional(readOnly = true) private List<ImageDeletionStruct> getImageOnBackupStorage(List<String> bsUuids) { String sql = "select ref.backupStorageUuid, img from ImageVO img, ImageBackupStorageRefVO ref where img.uuid = ref.imageUuid and ref.backupStorageUuid in (:bsUuids) group by img.uuid"; TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class); q.setParameter("bsUuids", bsUuids); List<Tuple> ts = q.getResultList(); Map<String, ImageDeletionStruct> tmp = new HashMap<String, ImageDeletionStruct>(); for (Tuple t : ts) { String bsUuid = t.get(0, String.class); ImageVO img = t.get(1, ImageVO.class); ImageDeletionStruct struct = tmp.get(img.getUuid()); if (struct == null) { struct = new ImageDeletionStruct(); struct.setImage(ImageInventory.valueOf(img)); struct.setBackupStorageUuids(new ArrayList<String>()); tmp.put(img.getUuid(), struct); } struct.getBackupStorageUuids().add(bsUuid); } List<ImageDeletionStruct> structs = new ArrayList<ImageDeletionStruct>(); structs.addAll(tmp.values()); return structs; } private ImageDeletionPolicy deletionPolicyFromAction(CascadeAction action) { if (BackupStorageVO.class.getSimpleName().equals(action.getParentIssuer())) { return ImageDeletionPolicy.DeleteReference; } else { return null; } } private List<ImageDeletionStruct> imageFromAction(CascadeAction action) { List<ImageDeletionStruct> ret = null; if (BackupStorageVO.class.getSimpleName().equals(action.getParentIssuer())) { List<String> bsuuids = CollectionUtils.transformToList((List<BackupStorageInventory>)action.getParentIssuerContext(), new Function<String, BackupStorageInventory>() { @Override public String call(BackupStorageInventory arg) { return arg.getUuid(); } }); ret = getImageOnBackupStorage(bsuuids); ret = ret.isEmpty() ? null : ret; } else if (NAME.equals(action.getParentIssuer())) { ret = action.getParentIssuerContext(); } else if (AccountVO.class.getSimpleName().equals(action.getParentIssuer())) { final List<String> auuids = CollectionUtils.transformToList((List<AccountInventory>) action.getParentIssuerContext(), new Function<String, AccountInventory>() { @Override public String call(AccountInventory arg) { return arg.getUuid(); } }); List<ImageVO> imgvos = new Callable<List<ImageVO>>() { @Override @Transactional(readOnly = true) public List<ImageVO> call() { String sql = "select d from ImageVO d, AccountResourceRefVO r where d.uuid = r.resourceUuid and" + " r.resourceType = :rtype and r.accountUuid in (:auuids)"; TypedQuery<ImageVO> q = dbf.getEntityManager().createQuery(sql, ImageVO.class); q.setParameter("auuids", auuids); q.setParameter("rtype", ImageVO.class.getSimpleName()); return q.getResultList(); } }.call(); if (!imgvos.isEmpty()) { ret = CollectionUtils.transformToList(imgvos, new Function<ImageDeletionStruct, ImageVO>() { @Override public ImageDeletionStruct call(ImageVO arg) { ImageDeletionStruct s = new ImageDeletionStruct(); s.setImage(ImageInventory.valueOf(arg)); return s; } }); } } return ret; } @Override public CascadeAction createActionForChildResource(CascadeAction action) { if (CascadeConstant.DELETION_CODES.contains(action.getActionCode())) { List<ImageDeletionStruct> ctx = imageFromAction(action); if (ctx != null) { return action.copy().setParentIssuer(NAME).setParentIssuerContext(ctx); } } return null; } }