package org.zstack.storage.primary.local;
import org.springframework.beans.factory.annotation.Autowired;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.db.*;
import org.zstack.core.db.SimpleQuery.Op;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.header.apimediator.ApiMessageInterceptionException;
import org.zstack.header.apimediator.ApiMessageInterceptor;
import org.zstack.header.apimediator.StopRoutingException;
import org.zstack.header.errorcode.SysErrors;
import org.zstack.header.host.HostInventory;
import org.zstack.header.host.HostVO;
import org.zstack.header.host.HostVO_;
import org.zstack.header.message.APIMessage;
import org.zstack.header.storage.primary.*;
import org.zstack.header.vm.VmInstanceState;
import org.zstack.header.vm.VmInstanceVO;
import org.zstack.header.vm.VmInstanceVO_;
import org.zstack.header.volume.*;
import javax.persistence.Tuple;
import static org.zstack.core.Platform.argerr;
import static org.zstack.core.Platform.err;
import static org.zstack.core.Platform.operr;
import java.util.ArrayList;
import java.util.List;
/**
* Created by frank on 7/1/2015.
*/
public class LocalStorageApiInterceptor implements ApiMessageInterceptor {
@Autowired
private ErrorFacade errf;
@Autowired
private DatabaseFacade dbf;
@Autowired
private CloudBus bus;
@Override
public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionException {
if (msg instanceof APIAddLocalPrimaryStorageMsg) {
validate((APIAddLocalPrimaryStorageMsg) msg);
} else if (msg instanceof APILocalStorageMigrateVolumeMsg) {
validate((APILocalStorageMigrateVolumeMsg) msg);
} else if (msg instanceof APILocalStorageGetVolumeMigratableHostsMsg) {
validate((APILocalStorageGetVolumeMigratableHostsMsg) msg);
}
return msg;
}
private void validate(APILocalStorageGetVolumeMigratableHostsMsg msg) {
APILocalStorageGetVolumeMigratableReply reply = new APILocalStorageGetVolumeMigratableReply();
SimpleQuery<LocalStorageResourceRefVO> q = dbf.createQuery(LocalStorageResourceRefVO.class);
q.add(LocalStorageResourceRefVO_.resourceType, Op.EQ, VolumeVO.class.getSimpleName());
q.add(LocalStorageResourceRefVO_.resourceUuid, Op.EQ, msg.getVolumeUuid());
LocalStorageResourceRefVO ref = q.find();
if (ref == null) {
reply.setInventories(new ArrayList<HostInventory>());
bus.reply(msg, reply);
throw new StopRoutingException();
}
msg.setPrimaryStorageUuid(ref.getPrimaryStorageUuid());
}
private void validate(APILocalStorageMigrateVolumeMsg msg) {
new SQLBatch() {
@Override
protected void scripts() {
//1.confirm that volume is on local storage and not on the dest Host.
LocalStorageResourceRefVO ref = Q.New(LocalStorageResourceRefVO.class)
.eq(LocalStorageResourceRefVO_.resourceType,VolumeVO.class.getSimpleName())
.eq(LocalStorageResourceRefVO_.resourceUuid,msg.getVolumeUuid()).find();
if (ref == null) {
throw new ApiMessageInterceptionException(argerr("the volume[uuid:%s] is not on any local primary storage", msg.getVolumeUuid()));
}
if (ref.getHostUuid().equals(msg.getDestHostUuid())) {
throw new ApiMessageInterceptionException(argerr("the volume[uuid:%s] is already on the host[uuid:%s]", msg.getVolumeUuid(), msg.getDestHostUuid()));
}
//2.confirm primary storage is available.
PrimaryStorageVO vo = Q.New(PrimaryStorageVO.class).eq(PrimaryStorageVO_.uuid,ref.getPrimaryStorageUuid()).find();
if (vo == null) {
throw new ApiMessageInterceptionException(argerr("the primary storage[uuid:%s] is not found", msg.getPrimaryStorageUuid()));
}
if (vo.getState() == PrimaryStorageState.Disabled || vo.getState() == PrimaryStorageState.Maintenance) {
throw new ApiMessageInterceptionException(argerr("the primary storage[uuid:%s] is disabled or maintenance cold migrate is not allowed", ref.getPrimaryStorageUuid()));
}
//3.confirm the dest host belong to the local storage where the volume locates
List<LocalStorageHostRefVO> refs = Q.New(LocalStorageHostRefVO.class)
.eq(LocalStorageHostRefVO_.hostUuid,msg.getDestHostUuid())
.eq(LocalStorageHostRefVO_.primaryStorageUuid,ref.getPrimaryStorageUuid()).list();
if (refs.size() == 0) {
throw new ApiMessageInterceptionException(argerr("the dest host[uuid:%s] doesn't belong to the local primary storage[uuid:%s] where the" +
" volume[uuid:%s] locates", msg.getDestHostUuid(), ref.getPrimaryStorageUuid(), msg.getVolumeUuid()));
}
//4.confirm primary storage is available.
VolumeVO vol = Q.New(VolumeVO.class).eq(VolumeVO_.uuid,msg.getVolumeUuid()).find();
if (VolumeStatus.Ready != vol.getStatus()) {
throw new ApiMessageInterceptionException(argerr("the volume[uuid:%s] is not in status of Ready, cannot migrate it", msg.getVolumeUuid()));
}
//5.confirm that the data volume has detach the vm and the root volume will migrate to appropriate cluster.
if (vol.getType() == VolumeType.Data && vol.getVmInstanceUuid() != null) {
throw new ApiMessageInterceptionException(argerr("the data volume[uuid:%s, name: %s] is still attached on the VM[uuid:%s]. Please detach" +
" it before migration", vol.getUuid(), vol.getName(), vol.getVmInstanceUuid()));
} else if (vol.getType() == VolumeType.Root) {
VmInstanceState vmstate = Q.New(VmInstanceVO.class)
.select(VmInstanceVO_.state)
.eq(VmInstanceVO_.uuid,vol.getVmInstanceUuid()).findValue();
if (VmInstanceState.Stopped != vmstate) {
throw new ApiMessageInterceptionException(operr("the volume[uuid:%s] is the root volume of the vm[uuid:%s]. Currently the vm is in" +
" state of %s, please stop it before migration", vol.getUuid(), vol.getVmInstanceUuid(), vmstate));
}
long count = Q.New(VolumeVO.class)
.eq(VolumeVO_.type,VolumeType.Data)
.eq(VolumeVO_.vmInstanceUuid,vol.getVmInstanceUuid()).count();
if (count != 0) {
throw new ApiMessageInterceptionException(operr("the volume[uuid:%s] is the root volume of the vm[uuid:%s]. Currently the vm still" +
" has %s data volumes attached, please detach them before migration", vol.getUuid(), vol.getVmInstanceUuid(), count));
}
String clusterUuid = Q.New(HostVO.class).select(HostVO_.clusterUuid)
.eq(HostVO_.uuid,msg.getDestHostUuid()).findValue();
String originClusterUuid = Q.New(VmInstanceVO.class)
.select(VmInstanceVO_.clusterUuid)
.eq(VmInstanceVO_.uuid, vol.getVmInstanceUuid()).findValue();
if(originClusterUuid == null){
throw new ApiMessageInterceptionException(
err(SysErrors.INTERNAL,"The clusterUuid of vm[uuid:%s] cannot be null when migrate the root volume[uuid:%s, name: %s]",vol.getVmInstanceUuid(),vol.getUuid(),vol.getName()));
}
if(!originClusterUuid.equals(clusterUuid)){
List<String> originL2NetworkList = sql("select l2NetworkUuid from L3NetworkVO" +
" where uuid in(select l3NetworkUuid from VmNicVO where vmInstanceUuid = :vmUuid)")
.param("vmUuid",vol.getVmInstanceUuid()).list();
List<String> l2NetworkList = sql("select l2NetworkUuid from L2NetworkClusterRefVO" +
" where clusterUuid = :clusterUuid")
.param("clusterUuid",clusterUuid).list();
for(String l2:originL2NetworkList){
if(!l2NetworkList.contains(l2)){
throw new ApiMessageInterceptionException(
operr("The two clusters[uuid:%s,uuid:%s] cannot access each other in l2 network when migrate the vm[uuid;%s] to another cluster", originClusterUuid, clusterUuid, vol.getVmInstanceUuid()));
}
}
}
msg.setPrimaryStorageUuid(ref.getPrimaryStorageUuid());
}
}
}.execute();
}
private void validate(APIAddLocalPrimaryStorageMsg msg) {
String url = msg.getUrl();
if (!url.startsWith("/")) {
throw new ApiMessageInterceptionException(argerr("the url[%s] is not an absolute path starting with '/'", msg.getUrl()));
}
if (url.startsWith("/dev") || url.startsWith("/proc") || url.startsWith("/sys")) {
throw new ApiMessageInterceptionException(argerr(" the url contains an invalid folder[/dev or /proc or /sys]"));
}
}
}