package org.zstack.storage.primary.local;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.compute.vm.VmAllocatePrimaryStorageFlow;
import org.zstack.compute.vm.VmAllocatePrimaryStorageForAttachingDiskFlow;
import org.zstack.compute.vm.VmMigrateOnHypervisorFlow;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.cloudbus.CloudBusCallBack;
import org.zstack.core.cloudbus.CloudBusListCallBack;
import org.zstack.core.componentloader.PluginRegistry;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.db.SimpleQuery.Op;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.header.Component;
import org.zstack.header.core.FutureCompletion;
import org.zstack.header.core.ReturnValueCompletion;
import org.zstack.header.core.workflow.*;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.errorcode.OperationFailureException;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.host.*;
import org.zstack.header.image.ImagePlatform;
import org.zstack.header.message.MessageReply;
import org.zstack.header.query.AddExpandedQueryExtensionPoint;
import org.zstack.header.query.ExpandedQueryAliasStruct;
import org.zstack.header.query.ExpandedQueryStruct;
import org.zstack.header.storage.backup.BackupStorageAskInstallPathMsg;
import org.zstack.header.storage.backup.BackupStorageAskInstallPathReply;
import org.zstack.header.storage.backup.BackupStorageConstant;
import org.zstack.header.storage.backup.DeleteBitsOnBackupStorageMsg;
import org.zstack.header.storage.primary.*;
import org.zstack.header.storage.snapshot.CreateTemplateFromVolumeSnapshotExtensionPoint;
import org.zstack.header.storage.snapshot.VolumeSnapshotInventory;
import org.zstack.header.vm.*;
import org.zstack.header.vm.VmInstanceConstant.VmOperation;
import org.zstack.header.volume.*;
import org.zstack.kvm.KVMConstant;
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.TypedQuery;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import static org.zstack.core.Platform.argerr;
import static org.zstack.core.Platform.operr;
import static org.zstack.utils.CollectionDSL.list;
/**
* Created by frank on 6/30/2015.
*/
public class LocalStorageFactory implements PrimaryStorageFactory, Component,
MarshalVmOperationFlowExtensionPoint, HostDeleteExtensionPoint, VmAttachVolumeExtensionPoint,
GetAttachableVolumeExtensionPoint, RecalculatePrimaryStorageCapacityExtensionPoint, HostMaintenancePolicyExtensionPoint,
AddExpandedQueryExtensionPoint, VolumeGetAttachableVmExtensionPoint, RecoverDataVolumeExtensionPoint,
RecoverVmExtensionPoint, VmPreMigrationExtensionPoint, CreateTemplateFromVolumeSnapshotExtensionPoint, HostAfterConnectedExtensionPoint,
InstantiateDataVolumeOnCreationExtensionPoint, PrimaryStorageAttachExtensionPoint {
private final static CLogger logger = Utils.getLogger(LocalStorageFactory.class);
public static PrimaryStorageType type = new PrimaryStorageType(LocalStorageConstants.LOCAL_STORAGE_TYPE);
static {
type.setSupportVmLiveMigration(false);
type.setSupportVolumeMigration(true);
type.setSupportVolumeMigrationInCurrentPrimaryStorage(true);
type.setOrder(999);
}
@Autowired
private DatabaseFacade dbf;
@Autowired
private PluginRegistry pluginRgty;
@Autowired
private CloudBus bus;
@Autowired
private ErrorFacade errf;
private Map<String, LocalStorageBackupStorageMediator> backupStorageMediatorMap = new HashMap<String, LocalStorageBackupStorageMediator>();
@Override
public WorkflowTemplate createTemplateFromVolumeSnapshot(final ParamIn paramIn) {
WorkflowTemplate template = new WorkflowTemplate();
class Context {
String temporaryInstallPath;
String hostUuid;
}
final Context ctx = new Context();
template.setCreateTemporaryTemplate(new Flow() {
String __name__ = "create-temporary-template";
@Override
public void run(final FlowTrigger trigger, final Map data) {
CreateTemporaryVolumeFromSnapshotMsg msg = new CreateTemporaryVolumeFromSnapshotMsg();
msg.setSnapshot(paramIn.getSnapshot());
msg.setPrimaryStorageUuid(paramIn.getPrimaryStorageUuid());
msg.setImageUuid(paramIn.getImage().getUuid());
bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, paramIn.getPrimaryStorageUuid());
bus.send(msg, new CloudBusCallBack(trigger) {
@Override
public void run(MessageReply reply) {
if (!reply.isSuccess()) {
trigger.fail(reply.getError());
} else {
ParamOut out = (ParamOut) data.get(ParamOut.class);
CreateTemporaryVolumeFromSnapshotReply r = reply.castReply();
out.setActualSize(r.getActualSize());
out.setSize(r.getSize());
ctx.temporaryInstallPath = r.getInstallPath();
ctx.hostUuid = r.getHostUuid();
trigger.next();
}
}
});
}
@Override
public void rollback(FlowRollback trigger, Map data) {
if (ctx.temporaryInstallPath != null) {
LocalStorageDirectlyDeleteBitsMsg msg = new LocalStorageDirectlyDeleteBitsMsg();
msg.setPrimaryStorageUuid(paramIn.getPrimaryStorageUuid());
msg.setHostUuid(ctx.hostUuid);
msg.setPath(ctx.temporaryInstallPath);
bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, paramIn.getPrimaryStorageUuid());
bus.send(msg);
}
trigger.rollback();
}
});
template.setUploadToBackupStorage(new Flow() {
String __name__ = "upload-to-backup-storage";
@Override
public void run(final FlowTrigger trigger, final Map data) {
BackupStorageAskInstallPathMsg ask = new BackupStorageAskInstallPathMsg();
ask.setBackupStorageUuid(paramIn.getBackupStorageUuid());
ask.setImageMediaType(paramIn.getImage().getMediaType());
ask.setImageUuid(paramIn.getImage().getUuid());
bus.makeTargetServiceIdByResourceUuid(ask, BackupStorageConstant.SERVICE_ID, paramIn.getBackupStorageUuid());
MessageReply areply = bus.call(ask);
if (!areply.isSuccess()) {
trigger.fail(areply.getError());
return;
}
String bsInstallPath = ((BackupStorageAskInstallPathReply) areply).getInstallPath();
UploadBitsFromLocalStorageToBackupStorageMsg msg = new UploadBitsFromLocalStorageToBackupStorageMsg();
msg.setHostUuid(ctx.hostUuid);
msg.setPrimaryStorageInstallPath(ctx.temporaryInstallPath);
msg.setPrimaryStorageUuid(paramIn.getPrimaryStorageUuid());
msg.setBackupStorageUuid(paramIn.getBackupStorageUuid());
msg.setBackupStorageInstallPath(bsInstallPath);
bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, paramIn.getPrimaryStorageUuid());
bus.send(msg, new CloudBusCallBack(trigger) {
@Override
public void run(MessageReply reply) {
ParamOut out = (ParamOut) data.get(ParamOut.class);
if (!reply.isSuccess()) {
trigger.fail(reply.getError());
} else {
UploadBitsFromLocalStorageToBackupStorageReply r = reply.castReply();
out.setBackupStorageInstallPath(r.getBackupStorageInstallPath());
trigger.next();
}
}
});
}
@Override
public void rollback(FlowRollback trigger, Map data) {
ParamOut out = (ParamOut) data.get(ParamOut.class);
if (out.getBackupStorageInstallPath() != null) {
DeleteBitsOnBackupStorageMsg msg = new DeleteBitsOnBackupStorageMsg();
msg.setBackupStorageUuid(paramIn.getBackupStorageUuid());
msg.setInstallPath(out.getBackupStorageInstallPath());
bus.makeTargetServiceIdByResourceUuid(msg, BackupStorageConstant.SERVICE_ID, paramIn.getBackupStorageUuid());
bus.send(msg);
}
trigger.rollback();
}
});
template.setDeleteTemporaryTemplate(new NoRollbackFlow() {
String __name__ = "delete-temporary-volume";
@Override
public void run(FlowTrigger trigger, Map data) {
LocalStorageDirectlyDeleteBitsMsg msg = new LocalStorageDirectlyDeleteBitsMsg();
msg.setHostUuid(ctx.hostUuid);
msg.setPath(ctx.temporaryInstallPath);
msg.setPrimaryStorageUuid(paramIn.getPrimaryStorageUuid());
bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, paramIn.getPrimaryStorageUuid());
bus.send(msg);
trigger.next();
}
});
return template;
}
@Override
public String createTemplateFromVolumeSnapshotPrimaryStorageType() {
return type.toString();
}
@Override
public String getPrimaryStorageTypeForRecalculateCapacityExtensionPoint() {
return type.toString();
}
@Override
public void afterRecalculatePrimaryStorageCapacity(RecalculatePrimaryStorageCapacityStruct struct) {
new LocalStorageCapacityRecalculator().calculateByPrimaryStorageUuid(struct.getPrimaryStorageUuid());
}
@Override
public void beforeRecalculatePrimaryStorageCapacity(RecalculatePrimaryStorageCapacityStruct struct) {
new LocalStorageCapacityRecalculator().calculateTotalCapacity(struct.getPrimaryStorageUuid());
}
@Override
public PrimaryStorageType getPrimaryStorageType() {
return type;
}
@Override
public PrimaryStorageInventory createPrimaryStorage(PrimaryStorageVO vo, APIAddPrimaryStorageMsg msg) {
vo.setMountPath(msg.getUrl());
vo = dbf.persistAndRefresh(vo);
return PrimaryStorageInventory.valueOf(vo);
}
@Override
public PrimaryStorage getPrimaryStorage(PrimaryStorageVO vo) {
return new LocalStorageBase(vo);
}
@Override
public PrimaryStorageInventory getInventory(String uuid) {
return PrimaryStorageInventory.valueOf(dbf.findByUuid(uuid, PrimaryStorageVO.class));
}
private String makeMediatorKey(String hvType, String bsType) {
return hvType + "-" + bsType;
}
public LocalStorageBackupStorageMediator getBackupStorageMediator(String hvType, String bsType) {
LocalStorageBackupStorageMediator m = backupStorageMediatorMap.get(makeMediatorKey(hvType, bsType));
if (m == null) {
throw new CloudRuntimeException(String.format("no LocalStorageBackupStorageMediator supporting hypervisor[%s] and backup storage[%s] ",
hvType, bsType));
}
return m;
}
@Override
public boolean start() {
for (LocalStorageBackupStorageMediator m : pluginRgty.getExtensionList(LocalStorageBackupStorageMediator.class)) {
for (String hvType : m.getSupportedHypervisorTypes()) {
String key = makeMediatorKey(hvType, m.getSupportedBackupStorageType().toString());
LocalStorageBackupStorageMediator old = backupStorageMediatorMap.get(key);
if (old != null) {
throw new CloudRuntimeException(String.format("duplicate LocalStorageBackupStorageMediator[%s, %s]" +
" for hypervisor type[%s] and backup storage type[%s]",
m, old, hvType, m.getSupportedBackupStorageType()));
}
backupStorageMediatorMap.put(key, m);
}
}
return true;
}
@Override
public boolean stop() {
return true;
}
@Transactional(readOnly = true)
private List<String> getLocalStorageInCluster(String clusterUuid) {
String sql = "select pri.uuid" +
" from PrimaryStorageVO pri, PrimaryStorageClusterRefVO ref" +
" where pri.uuid = ref.primaryStorageUuid" +
" and ref.clusterUuid = :cuuid" +
" and pri.type = :ptype";
TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("cuuid", clusterUuid);
q.setParameter("ptype", LocalStorageConstants.LOCAL_STORAGE_TYPE);
return q.getResultList();
}
private boolean isRootVolumeOnLocalStorage(String rootVolumeUuid) {
SimpleQuery<LocalStorageResourceRefVO> q = dbf.createQuery(LocalStorageResourceRefVO.class);
q.add(LocalStorageResourceRefVO_.resourceUuid, Op.EQ, rootVolumeUuid);
return q.isExists();
}
@Override
public Flow marshalVmOperationFlow(String previousFlowName, String nextFlowName, FlowChain chain, VmInstanceSpec spec) {
if (VmAllocatePrimaryStorageFlow.class.getName().equals(nextFlowName)) {
if (spec.getCurrentVmOperation() == VmOperation.NewCreate) {
List<String> localStorageUuids = getLocalStorageInCluster(spec.getDestHost().getClusterUuid());
if (localStorageUuids != null && !localStorageUuids.isEmpty()) {
return new LocalStorageAllocateCapacityFlow();
}
}
} else if (spec.getCurrentVmOperation() == VmOperation.AttachVolume) {
VolumeInventory volume = spec.getDestDataVolumes().get(0);
if (VolumeStatus.NotInstantiated.toString().equals(volume.getStatus())
&& VmAllocatePrimaryStorageForAttachingDiskFlow.class.getName().equals(nextFlowName)) {
if (isRootVolumeOnLocalStorage(spec.getVmInventory().getRootVolumeUuid())) {
return new LocalStorageAllocateCapacityForAttachingVolumeFlow();
}
}
} else if (spec.getCurrentVmOperation() == VmOperation.Migrate && isRootVolumeOnLocalStorage(spec.getVmInventory().getRootVolumeUuid())
&& VmMigrateOnHypervisorFlow.class.getName().equals(nextFlowName)) {
if (KVMConstant.KVM_HYPERVISOR_TYPE.equals(spec.getVmInventory().getHypervisorType())) {
return new LocalStorageKvmMigrateVmFlow();
} else {
throw new OperationFailureException(operr("local storage doesn't support live migration for hypervisor[%s]", spec.getVmInventory().getHypervisorType()));
}
}
return null;
}
@Override
public void preDeleteHost(HostInventory inventory) throws HostException {
}
@Override
public void beforeDeleteHost(final HostInventory inventory) {
// TODO re-write and add notifications to affected resoruces
// TODO move the logic to cascade extension
SimpleQuery<LocalStorageHostRefVO> q = dbf.createQuery(LocalStorageHostRefVO.class);
q.select(LocalStorageHostRefVO_.primaryStorageUuid);
q.add(LocalStorageHostRefVO_.hostUuid, Op.EQ, inventory.getUuid());
List<String> psUuids = q.listValue();
if (psUuids == null || psUuids.isEmpty()) {
return;
}
logger.debug(String.format("the host[uuid:%s] belongs to the local storage[uuid:%s], starts to delete vms and" +
" volumes on the host", inventory.getUuid(), String.join(",", psUuids)));
final List<String> vmUuids = new Callable<List<String>>() {
@Override
@Transactional(readOnly = true)
public List<String> call() {
String sql = "select vm.uuid" +
" from VolumeVO vol, LocalStorageResourceRefVO ref, VmInstanceVO vm" +
" where ref.primaryStorageUuid in :psUuids" +
" and vol.type = :vtype" +
" and ref.resourceUuid = vol.uuid" +
" and ref.resourceType = :rtype" +
" and ref.hostUuid = :huuid" +
" and vm.uuid = vol.vmInstanceUuid";
TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("vtype", VolumeType.Root);
q.setParameter("rtype", VolumeVO.class.getSimpleName());
q.setParameter("huuid", inventory.getUuid());
q.setParameter("psUuids", psUuids);
return q.getResultList();
}
}.call();
// destroy vms
if (!vmUuids.isEmpty()) {
List<DestroyVmInstanceMsg> msgs = CollectionUtils.transformToList(vmUuids, new Function<DestroyVmInstanceMsg, String>() {
@Override
public DestroyVmInstanceMsg call(String uuid) {
DestroyVmInstanceMsg msg = new DestroyVmInstanceMsg();
msg.setVmInstanceUuid(uuid);
bus.makeTargetServiceIdByResourceUuid(msg, VmInstanceConstant.SERVICE_ID, uuid);
return msg;
}
});
final FutureCompletion completion = new FutureCompletion(null);
bus.send(msgs, new CloudBusListCallBack(completion) {
@Override
public void run(List<MessageReply> replies) {
for (MessageReply r : replies) {
if (!r.isSuccess()) {
String vmUuid = vmUuids.get(replies.indexOf(r));
logger.warn(String.format("failed to destroy the vm[uuid:%s], %s", vmUuid, r.getError()));
}
}
completion.success();
}
});
completion.await(TimeUnit.MINUTES.toMillis(15));
}
final List<String> volUuids = new Callable<List<String>>() {
@Override
@Transactional(readOnly = true)
public List<String> call() {
String sql = "select vol.uuid" +
" from VolumeVO vol, LocalStorageResourceRefVO ref" +
" where ref.primaryStorageUuid in :psUuids" +
" and vol.type = :vtype" +
" and ref.resourceUuid = vol.uuid" +
" and ref.resourceType = :rtype" +
" and ref.hostUuid = :huuid";
TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("psUuids", psUuids);
q.setParameter("vtype", VolumeType.Data);
q.setParameter("rtype", VolumeVO.class.getSimpleName());
q.setParameter("huuid", inventory.getUuid());
return q.getResultList();
}
}.call();
// delete data volumes
if (!volUuids.isEmpty()) {
List<DeleteVolumeMsg> msgs = CollectionUtils.transformToList(volUuids, new Function<DeleteVolumeMsg, String>() {
@Override
public DeleteVolumeMsg call(String uuid) {
DeleteVolumeMsg msg = new DeleteVolumeMsg();
msg.setUuid(uuid);
msg.setDetachBeforeDeleting(true);
msg.setDeletionPolicy(VolumeDeletionPolicyManager.VolumeDeletionPolicy.DBOnly.toString());
bus.makeTargetServiceIdByResourceUuid(msg, VolumeConstant.SERVICE_ID, uuid);
return msg;
}
});
final FutureCompletion completion = new FutureCompletion(null);
bus.send(msgs, new CloudBusListCallBack(completion) {
@Override
public void run(List<MessageReply> replies) {
for (MessageReply r : replies) {
if (!r.isSuccess()) {
String uuid = volUuids.get(replies.indexOf(r));
//TODO
logger.warn(String.format("failed to delete the data volume[uuid:%s], %s", uuid,
r.getError()));
}
}
completion.success();
}
});
completion.await(TimeUnit.MINUTES.toMillis(15));
}
final List<String> priUuids = getLocalStorageInCluster(inventory.getClusterUuid());
if (priUuids == null || priUuids.isEmpty()) {
return;
}
// decrease ps capacity
for (String priUuid : priUuids) {
RemoveHostFromLocalStorageMsg msg = new RemoveHostFromLocalStorageMsg();
msg.setPrimaryStorageUuid(priUuid);
msg.setHostUuid(inventory.getUuid());
bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, priUuid);
MessageReply reply = bus.call(msg);
if (!reply.isSuccess()) {
//TODO
logger.warn(String.format("failed to remove host[uuid:%s] from local primary storage[uuid:%s], %s",
inventory.getUuid(), priUuid, reply.getError()));
} else {
logger.debug(String.format("removed host[uuid:%s] from local primary storage[uuid:%s]",
inventory.getUuid(), priUuid));
}
}
}
@Override
public void afterDeleteHost(final HostInventory inventory) {
}
@Override
public void preAttachVolume(VmInstanceInventory vm, final VolumeInventory volume) {
SimpleQuery<LocalStorageResourceRefVO> q = dbf.createQuery(LocalStorageResourceRefVO.class);
q.add(LocalStorageResourceRefVO_.resourceUuid, Op.IN, list(vm.getRootVolumeUuid(), volume.getUuid()));
q.groupBy(LocalStorageResourceRefVO_.hostUuid);
long count = q.count();
if (count < 2) {
return;
}
q = dbf.createQuery(LocalStorageResourceRefVO.class);
q.select(LocalStorageResourceRefVO_.hostUuid);
q.add(LocalStorageResourceRefVO_.resourceUuid, Op.EQ, vm.getRootVolumeUuid());
String rootHost = q.findValue();
q = dbf.createQuery(LocalStorageResourceRefVO.class);
q.select(LocalStorageResourceRefVO_.hostUuid);
q.add(LocalStorageResourceRefVO_.resourceUuid, Op.EQ, volume.getUuid());
String dataHost = q.findValue();
if (!rootHost.equals(dataHost)) {
throw new OperationFailureException(operr("cannot attach the data volume[uuid:%s] to the vm[uuid:%s]." +
" Both vm's root volume and the data volume are" +
" on local primary storage, but they are on different hosts." +
" The root volume[uuid:%s] is on the host[uuid:%s] but the data volume[uuid: %s]" +
" is on the host[uuid: %s]",
volume.getUuid(), vm.getUuid(), vm.getRootVolumeUuid(),
rootHost, volume.getUuid(), dataHost));
}
}
@Override
public void beforeAttachVolume(VmInstanceInventory vm, VolumeInventory volume) {
}
@Override
public void afterAttachVolume(VmInstanceInventory vm, VolumeInventory volume) {
}
@Override
public void failedToAttachVolume(VmInstanceInventory vm, VolumeInventory volume, ErrorCode errorCode) {
}
@Override
@Transactional(readOnly = true)
public List<VolumeVO> returnAttachableVolumes(VmInstanceInventory vm, List<VolumeVO> candidates) {
// find instantiated volumes
List<String> volUuids = CollectionUtils.transformToList(candidates, new Function<String, VolumeVO>() {
@Override
public String call(VolumeVO arg) {
return VolumeStatus.Ready == arg.getStatus() ? arg.getUuid() : null;
}
});
if (volUuids.isEmpty()) {
return candidates;
}
List<VolumeVO> uninstantiatedVolumes = CollectionUtils.transformToList(candidates, new Function<VolumeVO, VolumeVO>() {
@Override
public VolumeVO call(VolumeVO arg) {
return arg.getStatus() == VolumeStatus.NotInstantiated ? arg : null;
}
});
String sql = "select ref.hostUuid" +
" from LocalStorageResourceRefVO ref" +
" where ref.resourceUuid = :volUuid" +
" and ref.resourceType = :rtype";
TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("volUuid", vm.getRootVolumeUuid());
q.setParameter("rtype", VolumeVO.class.getSimpleName());
List<String> ret = q.getResultList();
if (ret.isEmpty()) {
return candidates;
}
String hostUuid = ret.get(0);
sql = "select ref.resourceUuid" +
" from LocalStorageResourceRefVO ref" +
" where ref.resourceUuid in (:uuids)" +
" and ref.resourceType = :rtype" +
" and ref.hostUuid != :huuid";
q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("uuids", volUuids);
q.setParameter("huuid", hostUuid);
q.setParameter("rtype", VolumeVO.class.getSimpleName());
final List<String> toExclude = q.getResultList();
candidates = CollectionUtils.transformToList(candidates, new Function<VolumeVO, VolumeVO>() {
@Override
public VolumeVO call(VolumeVO arg) {
return toExclude.contains(arg.getUuid()) ? null : arg;
}
});
candidates.addAll(uninstantiatedVolumes);
return candidates;
}
@Override
@Transactional(readOnly = true)
public HostMaintenancePolicy getHostMaintenancePolicy(HostInventory host) {
String sql = "select count(ps)" +
" from PrimaryStorageVO ps, PrimaryStorageClusterRefVO ref" +
" where ps.uuid = ref.primaryStorageUuid" +
" and ps.type = :type" +
" and ref.clusterUuid = :cuuid";
TypedQuery<Long> q = dbf.getEntityManager().createQuery(sql, Long.class);
q.setParameter("type", LocalStorageConstants.LOCAL_STORAGE_TYPE);
q.setParameter("cuuid", host.getClusterUuid());
q.setMaxResults(1);
Long count = q.getSingleResult();
return count > 0 ? HostMaintenancePolicy.StopVm : null;
}
@Override
public List<ExpandedQueryStruct> getExpandedQueryStructs() {
List<ExpandedQueryStruct> structs = new ArrayList<>();
ExpandedQueryStruct s = new ExpandedQueryStruct();
s.setExpandedField("localStorageHostRef");
s.setExpandedInventoryKey("resourceUuid");
s.setForeignKey("uuid");
s.setInventoryClass(LocalStorageResourceRefInventory.class);
s.setInventoryClassToExpand(VolumeInventory.class);
structs.add(s);
s = new ExpandedQueryStruct();
s.setExpandedField("localStorageHostRef");
s.setExpandedInventoryKey("resourceUuid");
s.setForeignKey("uuid");
s.setInventoryClass(LocalStorageResourceRefInventory.class);
s.setInventoryClassToExpand(VolumeSnapshotInventory.class);
structs.add(s);
return structs;
}
@Override
public List<ExpandedQueryAliasStruct> getExpandedQueryAliasesStructs() {
return null;
}
@Override
@Transactional(readOnly = true)
public List<VmInstanceVO> returnAttachableVms(VolumeInventory vol, List<VmInstanceVO> candidates) {
String sql = "select ref.hostUuid" +
" from LocalStorageResourceRefVO ref" +
" where ref.resourceUuid = :uuid" +
" and ref.resourceType = :rtype";
TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("uuid", vol.getUuid());
q.setParameter("rtype", VolumeVO.class.getSimpleName());
List<String> ret = q.getResultList();
if (ret.isEmpty()) {
return candidates;
}
String hostUuid = ret.get(0);
List<String> vmRootVolumeUuids = CollectionUtils.transformToList(candidates, new Function<String, VmInstanceVO>() {
@Override
public String call(VmInstanceVO arg) {
return arg.getRootVolumeUuid();
}
});
sql = "select ref.resourceUuid" +
" from LocalStorageResourceRefVO ref" +
" where ref.hostUuid = :huuid" +
" and ref.resourceUuid in (:rootVolumeUuids)" +
" and ref.resourceType = :rtype";
q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("huuid", hostUuid);
q.setParameter("rootVolumeUuids", vmRootVolumeUuids);
q.setParameter("rtype", VolumeVO.class.getSimpleName());
final List<String> toInclude = q.getResultList();
candidates = CollectionUtils.transformToList(candidates, new Function<VmInstanceVO, VmInstanceVO>() {
@Override
public VmInstanceVO call(VmInstanceVO arg) {
return toInclude.contains(arg.getRootVolumeUuid()) ? arg : null;
}
});
return candidates;
}
@Override
public void preRecoverDataVolume(VolumeInventory vol) {
if (vol.getPrimaryStorageUuid() == null) {
return;
}
SimpleQuery<PrimaryStorageVO> q = dbf.createQuery(PrimaryStorageVO.class);
q.select(PrimaryStorageVO_.type);
q.add(PrimaryStorageVO_.uuid, Op.EQ, vol.getPrimaryStorageUuid());
String type = q.findValue();
if (!LocalStorageConstants.LOCAL_STORAGE_TYPE.equals(type)) {
return;
}
SimpleQuery<LocalStorageResourceRefVO> rq = dbf.createQuery(LocalStorageResourceRefVO.class);
rq.add(LocalStorageResourceRefVO_.resourceUuid, Op.EQ, vol.getUuid());
rq.add(LocalStorageResourceRefVO_.resourceType, Op.EQ, VolumeVO.class.getSimpleName());
if (!rq.isExists()) {
throw new OperationFailureException(operr("the data volume[name:%s, uuid:%s] is on the local storage[uuid:%s]; however," +
"the host on which the data volume is has been deleted. Unable to recover this volume",
vol.getName(), vol.getUuid(), vol.getPrimaryStorageUuid()));
}
}
@Override
public void beforeRecoverDataVolume(VolumeInventory vol) {
}
@Override
public void afterRecoverDataVolume(VolumeInventory vol) {
}
@Override
@Transactional(readOnly = true)
public void preRecoverVm(VmInstanceInventory vm) {
String rootVolUuid = vm.getRootVolumeUuid();
String sql = "select ps.uuid" +
" from PrimaryStorageVO ps, VolumeVO vol" +
" where ps.uuid = vol.primaryStorageUuid" +
" and vol.uuid = :uuid" +
" and ps.type = :pstype";
TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("uuid", rootVolUuid);
q.setParameter("pstype", LocalStorageConstants.LOCAL_STORAGE_TYPE);
String psuuid = dbf.find(q);
if (psuuid == null) {
return;
}
sql = "select count(ref)" +
" from LocalStorageResourceRefVO ref" +
" where ref.resourceUuid = :uuid" +
" and ref.resourceType = :rtype";
TypedQuery<Long> rq = dbf.getEntityManager().createQuery(sql, Long.class);
rq.setParameter("uuid", rootVolUuid);
rq.setParameter("rtype", VolumeVO.class.getSimpleName());
long count = rq.getSingleResult();
if (count == 0) {
throw new OperationFailureException(operr("unable to recover the vm[uuid:%s, name:%s]. The vm's root volume is on the local" +
" storage[uuid:%s]; however, the host on which the root volume is has been deleted",
vm.getUuid(), vm.getName(), psuuid));
}
}
@Override
public void beforeRecoverVm(VmInstanceInventory vm) {
}
@Override
public void afterRecoverVm(VmInstanceInventory vm) {
}
@Override
@Transactional(readOnly = true, noRollbackForClassName = {"org.zstack.header.errorcode.OperationFailureException"})
public void preVmMigration(VmInstanceInventory vm) {
if (!LocalStoragePrimaryStorageGlobalConfig.ALLOW_LIVE_MIGRATION.value(Boolean.class)) {
refuseLiveMigrationForLocalStorage(vm);
}
// forbid live migration with data volumes for local storage
if (vm.getAllVolumes().size() > 1) {
throw new OperationFailureException(operr("unable to live migrate vm[uuid:%s] with data volumes on local storage." +
" Need detach all data volumes first.", vm.getUuid()));
}
if (!ImagePlatform.Linux.toString().equals(vm.getPlatform())) {
throw new OperationFailureException(operr("unable to live migrate vm[uuid:%s] with local storage." +
" Only linux guest is supported. Current platform is [%s]", vm.getUuid(), vm.getPlatform()));
}
}
private void refuseLiveMigrationForLocalStorage(VmInstanceInventory vm) {
List<String> volUuids = CollectionUtils.transformToList(vm.getAllVolumes(), new Function<String, VolumeInventory>() {
@Override
public String call(VolumeInventory arg) {
return arg.getUuid();
}
});
String sql = "select count(ps)" +
" from PrimaryStorageVO ps, VolumeVO vol" +
" where ps.uuid = vol.primaryStorageUuid" +
" and vol.uuid in (:volUuids)" +
" and ps.type = :ptype";
TypedQuery<Long> q = dbf.getEntityManager().createQuery(sql, Long.class);
q.setParameter("volUuids", volUuids);
q.setParameter("ptype", LocalStorageConstants.LOCAL_STORAGE_TYPE);
q.setMaxResults(1);
Long count = q.getSingleResult();
if (count > 0) {
throw new OperationFailureException(operr("unable to live migrate with local storage. The vm[uuid:%s] has volumes on local storage," +
"to protect your data, please stop the vm and do the volume migration", vm.getUuid()));
}
}
@Override
public void afterHostConnected(HostInventory host) {
recalculatePrimaryStorageCapacity(host.getClusterUuid());
}
private void recalculatePrimaryStorageCapacity(String clusterUuid) {
SimpleQuery<PrimaryStorageClusterRefVO> q = dbf.createQuery(PrimaryStorageClusterRefVO.class);
q.add(PrimaryStorageClusterRefVO_.clusterUuid, Op.EQ, clusterUuid);
List<PrimaryStorageClusterRefVO> refs = q.list();
if (refs != null && !refs.isEmpty()) {
for (PrimaryStorageClusterRefVO ref : refs) {
RecalculatePrimaryStorageCapacityMsg msg = new RecalculatePrimaryStorageCapacityMsg();
msg.setPrimaryStorageUuid(ref.getPrimaryStorageUuid());
bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, ref.getPrimaryStorageUuid());
bus.send(msg);
}
}
}
@Override
public String getPrimaryStorageTypeForInstantiateDataVolumeOnCreationExtensionPoint() {
return LocalStorageConstants.LOCAL_STORAGE_TYPE;
}
@Override
public void instantiateDataVolumeOnCreation(InstantiateVolumeMsg msg, VolumeInventory volume, ReturnValueCompletion<VolumeInventory> completion) {
String hostUuid = null;
if (msg.getHostUuid() != null) {
hostUuid = msg.getHostUuid();
} else {
if (msg.getSystemTags() != null) {
for (String stag : msg.getSystemTags()) {
if (LocalStorageSystemTags.DEST_HOST_FOR_CREATING_DATA_VOLUME.isMatch(stag)) {
hostUuid = LocalStorageSystemTags.DEST_HOST_FOR_CREATING_DATA_VOLUME.getTokenByTag(
stag,
LocalStorageSystemTags.DEST_HOST_FOR_CREATING_DATA_VOLUME_TOKEN
);
break;
}
}
}
if (hostUuid == null) {
throw new OperationFailureException(argerr("To create data volume on the local primary storage, you must specify the host that" +
" the data volume is going to be created using the system tag [%s]",
LocalStorageSystemTags.DEST_HOST_FOR_CREATING_DATA_VOLUME.getTagFormat()));
}
}
SimpleQuery<LocalStorageHostRefVO> q = dbf.createQuery(LocalStorageHostRefVO.class);
q.add(LocalStorageHostRefVO_.hostUuid, Op.EQ, hostUuid);
q.add(LocalStorageHostRefVO_.primaryStorageUuid, Op.EQ, msg.getPrimaryStorageUuid());
if (!q.isExists()) {
throw new OperationFailureException(argerr("the host[uuid:%s] doesn't belong to the local primary storage[uuid:%s]", hostUuid, msg.getPrimaryStorageUuid()));
}
InstantiateVolumeOnPrimaryStorageMsg imsg;
if (msg instanceof InstantiateRootVolumeMsg) {
InstantiateRootVolumeFromTemplateOnPrimaryStorageMsg irmsg = new InstantiateRootVolumeFromTemplateOnPrimaryStorageMsg();
irmsg.setTemplateSpec(((InstantiateRootVolumeMsg) msg).getTemplateSpec());
imsg = irmsg;
} else {
imsg = new InstantiateVolumeOnPrimaryStorageMsg();
}
imsg.setVolume(volume);
imsg.setPrimaryStorageUuid(msg.getPrimaryStorageUuid());
imsg.setDestHost(HostInventory.valueOf(dbf.findByUuid(hostUuid, HostVO.class)));
bus.makeTargetServiceIdByResourceUuid(imsg, PrimaryStorageConstant.SERVICE_ID, msg.getPrimaryStorageUuid());
bus.send(imsg, new CloudBusCallBack(completion) {
@Override
public void run(MessageReply reply) {
if (!reply.isSuccess()) {
completion.fail(reply.getError());
} else {
completion.success(((InstantiateVolumeOnPrimaryStorageReply) reply).getVolume());
}
}
});
}
@Override
public void preAttachPrimaryStorage(PrimaryStorageInventory inventory, String clusterUuid) throws PrimaryStorageException {
}
@Override
public void beforeAttachPrimaryStorage(PrimaryStorageInventory inventory, String clusterUuid) {
}
@Override
public void failToAttachPrimaryStorage(PrimaryStorageInventory inventory, String clusterUuid) {
}
@Override
public void afterAttachPrimaryStorage(PrimaryStorageInventory inventory, String clusterUuid) {
recalculatePrimaryStorageCapacity(clusterUuid);
}
}