package org.zstack.storage.backup.sftp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.util.UriComponentsBuilder;
import org.zstack.core.CoreGlobalProperty;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.core.workflow.FlowChainBuilder;
import org.zstack.core.workflow.ShareFlow;
import org.zstack.header.core.workflow.FlowChain;
import org.zstack.header.core.workflow.FlowDoneHandler;
import org.zstack.header.core.workflow.FlowTrigger;
import org.zstack.header.core.workflow.NoRollbackFlow;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.errorcode.SysErrors;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.image.*;
import org.zstack.header.rest.JsonAsyncRESTCallback;
import org.zstack.header.rest.RESTFacade;
import org.zstack.header.storage.backup.AddBackupStorageExtensionPoint;
import org.zstack.header.storage.backup.AddBackupStorageStruct;
import org.zstack.utils.DebugUtils;
import org.zstack.utils.Utils;
import org.zstack.utils.gson.JSONObjectUtil;
import org.zstack.utils.logging.CLogger;
import static org.zstack.core.Platform.operr;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Created by Mei Lei <meilei007@gmail.com> on 11/3/16.
*/
public class SftpBackupStorageMetaDataMaker implements AddImageExtensionPoint, AddBackupStorageExtensionPoint, ExpungeImageExtensionPoint {
private static final CLogger logger = Utils.getLogger(SftpBackupStorageMetaDataMaker.class);
@Autowired
protected RESTFacade restf;
@Autowired
private DatabaseFacade dbf;
@Autowired
private ErrorFacade errf;
@Autowired
private SftpBackupStorageDumpMetadataInfo dumpInfo;
private String buildUrl(String subPath, String hostName) {
UriComponentsBuilder ub = UriComponentsBuilder.newInstance();
ub.scheme(SftpBackupStorageGlobalProperty.AGENT_URL_SCHEME);
if (CoreGlobalProperty.UNIT_TEST_ON) {
ub.host("localhost");
} else {
ub.host(hostName);
}
ub.port(SftpBackupStorageGlobalProperty.AGENT_PORT);
if (!"".equals(SftpBackupStorageGlobalProperty.AGENT_URL_ROOT_PATH)) {
ub.path(SftpBackupStorageGlobalProperty.AGENT_URL_ROOT_PATH);
}
ub.path(subPath);
return ub.build().toUriString();
}
@Transactional
private String getAllImageInventories(SftpBackupStorageDumpMetadataInfo dumpInfo) {
TypedQuery<ImageVO> q;
String allImageInventories = null;
ImageInventory img = dumpInfo.getImg();
String sql = "select img from ImageVO img where uuid in (select imageUuid from ImageBackupStorageRefVO ref where ref.backupStorageUuid= :bsUuid)";
q = dbf.getEntityManager().createQuery(sql, ImageVO.class);
if (dumpInfo.getImg() != null ) {
q.setParameter("bsUuid", getBackupStorageUuidFromImageInventory(img));
} else {
q.setParameter("bsUuid", dumpInfo.getBackupStorageUuid());
}
List<ImageVO> allImageVO = q.getResultList();
for (ImageVO imageVO : allImageVO) {
if (allImageInventories != null) {
allImageInventories = JSONObjectUtil.toJsonString(ImageInventory.valueOf(imageVO)) + "\n" + allImageInventories;
} else {
allImageInventories = JSONObjectUtil.toJsonString(ImageInventory.valueOf(imageVO));
}
}
return allImageInventories;
}
private void restoreImagesBackupStorageMetadataToDatabase(String imagesMetadata, String backupStorageUuid) {
List<ImageVO> imageVOs = new ArrayList<ImageVO>();
List<ImageBackupStorageRefVO> backupStorageRefVOs = new ArrayList<ImageBackupStorageRefVO>();
String[] metadatas = imagesMetadata.split("\n");
for (String metadata : metadatas) {
if (metadata.contains("backupStorageRefs")) {
ImageInventory imageInventory = JSONObjectUtil.toObject(metadata, ImageInventory.class);
for (ImageBackupStorageRefInventory ref : imageInventory.getBackupStorageRefs()) {
ImageBackupStorageRefVO backupStorageRefVO = new ImageBackupStorageRefVO();
backupStorageRefVO.setStatus(ImageStatus.valueOf(ref.getStatus()));
backupStorageRefVO.setInstallPath(ref.getInstallPath());
backupStorageRefVO.setImageUuid(ref.getImageUuid());
backupStorageRefVO.setBackupStorageUuid(backupStorageUuid);
backupStorageRefVO.setCreateDate(ref.getCreateDate());
backupStorageRefVO.setLastOpDate(ref.getLastOpDate());
backupStorageRefVOs.add(backupStorageRefVO);
}
ImageVO imageVO = new ImageVO();
imageVO.setActualSize(imageInventory.getActualSize());
imageVO.setDescription(imageInventory.getDescription());
imageVO.setStatus(ImageStatus.valueOf(imageInventory.getStatus()));
imageVO.setExportUrl(imageInventory.getExportUrl());
imageVO.setFormat(imageInventory.getFormat());
imageVO.setGuestOsType(imageInventory.getGuestOsType());
imageVO.setMd5Sum(imageInventory.getMd5Sum());
imageVO.setMediaType(ImageConstant.ImageMediaType.valueOf(imageInventory.getMediaType()));
imageVO.setName(imageInventory.getName());
imageVO.setPlatform(ImagePlatform.valueOf(imageInventory.getPlatform()));
imageVO.setSize(imageInventory.getSize());
imageVO.setState(ImageState.valueOf(imageInventory.getState()));
imageVO.setSystem(imageInventory.isSystem());
imageVO.setType(imageInventory.getType());
imageVO.setUrl(imageInventory.getUrl());
imageVO.setUuid(imageInventory.getUuid());
imageVO.setCreateDate(imageInventory.getCreateDate());
imageVO.setLastOpDate(imageInventory.getLastOpDate());
imageVOs.add(imageVO);
}
}
dbf.persistCollection(imageVOs);
dbf.persistCollection(backupStorageRefVOs);
}
private String getBsUrlFromImageInventory(ImageInventory img) {
SimpleQuery<ImageBackupStorageRefVO> q = dbf.createQuery(ImageBackupStorageRefVO.class);
q.select(ImageBackupStorageRefVO_.backupStorageUuid);
q.add(ImageBackupStorageRefVO_.imageUuid, SimpleQuery.Op.EQ, img.getUuid());
List<String> bsUuids = q.listValue();
if (bsUuids.isEmpty()) {
return null;
}
String bsUuid = bsUuids.get(0);
SimpleQuery<SftpBackupStorageVO> q2 = dbf.createQuery(SftpBackupStorageVO.class);
q2.select(SftpBackupStorageVO_.url);
q2.add(SftpBackupStorageVO_.uuid, SimpleQuery.Op.EQ, bsUuid);
List<String> urls = q2.listValue();
if (urls.isEmpty()) {
return null;
}
return urls.get(0);
}
private String getHostNameFromImageInventory(ImageInventory img) {
SimpleQuery<ImageBackupStorageRefVO> q = dbf.createQuery(ImageBackupStorageRefVO.class);
q.select(ImageBackupStorageRefVO_.backupStorageUuid);
q.add(ImageBackupStorageRefVO_.imageUuid, SimpleQuery.Op.EQ, img.getUuid());
List<String> bsUuids = q.listValue();
if (bsUuids.isEmpty()) {
throw new CloudRuntimeException("Didn't find any available backup storage");
}
String bsUuid = bsUuids.get(0);
SimpleQuery<SftpBackupStorageVO> q2 = dbf.createQuery(SftpBackupStorageVO.class);
q2.select(SftpBackupStorageVO_.hostname);
q2.add(SftpBackupStorageVO_.uuid, SimpleQuery.Op.EQ, bsUuid);
List<String> hostNames = q2.listValue();
if (hostNames.isEmpty()) {
throw new CloudRuntimeException("Didn't find any available hostname");
}
return hostNames.get(0);
}
@Transactional
private String getBackupStorageTypeFromImageInventory(ImageInventory img) {
String sql = "select bs.type from BackupStorageVO bs, ImageBackupStorageRefVO refVo where " +
"bs.uuid = refVo.backupStorageUuid and refVo.imageUuid = :uuid";
TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("uuid", img.getUuid());
String type = q.getSingleResult();
DebugUtils.Assert(type != null, String.format("cannot find backupStorage type for image [uuid:%s]", img.getUuid()));
return type;
}
private String getBackupStorageUuidFromImageInventory(ImageInventory img) {
SimpleQuery<ImageBackupStorageRefVO> q = dbf.createQuery(ImageBackupStorageRefVO.class);
q.select(ImageBackupStorageRefVO_.backupStorageUuid);
q.add(ImageBackupStorageRefVO_.imageUuid, SimpleQuery.Op.EQ, img.getUuid());
String backupStorageUuid = q.findValue();
DebugUtils.Assert(backupStorageUuid != null, String.format("cannot find backup storage for image [uuid:%s]", img.getUuid()));
return backupStorageUuid;
}
protected void dumpImagesBackupStorageInfoToMetaDataFile(SftpBackupStorageDumpMetadataInfo dumpInfo) {
logger.debug("dump all images info to meta data file");
boolean allImagesInfo = dumpInfo.getDumpAllInfo();
ImageInventory img = dumpInfo.getImg();
String bsUrl = dumpInfo.getBackupStorageUrl();
String hostName = dumpInfo.getBackupStorageHostname();
SftpBackupStorageCommands.DumpImageInfoToMetaDataFileCmd dumpCmd = new SftpBackupStorageCommands.DumpImageInfoToMetaDataFileCmd();
String metaData;
if (allImagesInfo) {
metaData = getAllImageInventories(dumpInfo);
} else {
metaData = JSONObjectUtil.toJsonString(img);
}
dumpCmd.setImageMetaData(metaData);
dumpCmd.setDumpAllMetaData(allImagesInfo);
if (bsUrl != null) {
dumpCmd.setBackupStoragePath(bsUrl);
} else {
dumpCmd.setBackupStoragePath(getBsUrlFromImageInventory(img));
}
if (hostName == null || hostName.isEmpty()) {
hostName = getHostNameFromImageInventory(img);
}
restf.asyncJsonPost(buildUrl(SftpBackupStorageConstant.DUMP_IMAGE_METADATA_TO_FILE, hostName), dumpCmd,
new JsonAsyncRESTCallback<SftpBackupStorageCommands.DumpImageInfoToMetaDataFileRsp>(null) {
@Override
public void fail(ErrorCode err) {
logger.error("dump image metadata failed" + err.toString());
}
@Override
public void success(SftpBackupStorageCommands.DumpImageInfoToMetaDataFileRsp rsp) {
if (!rsp.isSuccess()) {
logger.error("dump image metadata failed");
} else {
logger.info("dump image metadata successfully");
}
}
@Override
public Class<SftpBackupStorageCommands.DumpImageInfoToMetaDataFileRsp> getReturnClass() {
return SftpBackupStorageCommands.DumpImageInfoToMetaDataFileRsp.class;
}
});
}
@Override
public void preAddImage(ImageInventory img) {
}
@Override
public void beforeAddImage(ImageInventory img) {
}
@Override
public void afterAddImage(ImageInventory img) {
if (!getBackupStorageTypeFromImageInventory(img).equals(SftpBackupStorageConstant.SFTP_BACKUP_STORAGE_TYPE)) {
return;
}
FlowChain chain = FlowChainBuilder.newShareFlowChain();
chain.setName("add-image-metadata-to-backupStorage-file");
chain.then(new ShareFlow() {
boolean metaDataExist = false;
@Override
public void setup() {
flow(new NoRollbackFlow() {
String __name__ = "check-image-metadata-file-exist";
@Override
public void run(FlowTrigger trigger, Map data) {
SftpBackupStorageCommands.CheckImageMetaDataFileExistCmd cmd = new SftpBackupStorageCommands.CheckImageMetaDataFileExistCmd();
cmd.setBackupStoragePath(getBsUrlFromImageInventory(img));
restf.asyncJsonPost(buildUrl(SftpBackupStorageConstant.CHECK_IMAGE_METADATA_FILE_EXIST, getHostNameFromImageInventory(img)), cmd,
new JsonAsyncRESTCallback<SftpBackupStorageCommands.CheckImageMetaDataFileExistRsp>(trigger) {
@Override
public void fail(ErrorCode err) {
logger.error("check image metadata file exist failed" + err.toString());
trigger.fail(err);
}
@Override
public void success(SftpBackupStorageCommands.CheckImageMetaDataFileExistRsp rsp) {
if (!rsp.isSuccess()) {
logger.error(String.format("check image metadata file: %s failed", rsp.getBackupStorageMetaFileName()));
ErrorCode ec = operr("check image metadata file: %s failed", rsp.getBackupStorageMetaFileName());
trigger.fail(ec);
} else {
if (!rsp.getExist()) {
logger.info(String.format("image metadata file %s is not exist", rsp.getBackupStorageMetaFileName()));
// call generate and dump all image info to yaml
trigger.next();
} else {
logger.info(String.format("image metadata file %s exist", rsp.getBackupStorageMetaFileName()));
metaDataExist = true;
trigger.next();
}
}
}
@Override
public Class<SftpBackupStorageCommands.CheckImageMetaDataFileExistRsp> getReturnClass() {
return SftpBackupStorageCommands.CheckImageMetaDataFileExistRsp.class;
}
});
}
});
flow(new NoRollbackFlow() {
String __name__ = "create-image-metadata-file";
@Override
public void run(FlowTrigger trigger, Map data) {
if (!metaDataExist) {
SftpBackupStorageCommands.GenerateImageMetaDataFileCmd generateCmd = new SftpBackupStorageCommands.GenerateImageMetaDataFileCmd();
generateCmd.setBackupStoragePath(getBsUrlFromImageInventory(img));
restf.asyncJsonPost(buildUrl(SftpBackupStorageConstant.GENERATE_IMAGE_METADATA_FILE, getHostNameFromImageInventory(img)), generateCmd,
new JsonAsyncRESTCallback<SftpBackupStorageCommands.GenerateImageMetaDataFileRsp>(trigger) {
@Override
public void fail(ErrorCode err) {
logger.error("create image metadata file failed" + err.toString());
}
@Override
public void success(SftpBackupStorageCommands.GenerateImageMetaDataFileRsp rsp) {
if (!rsp.isSuccess()) {
ErrorCode ec = operr("create image metadata file : %s failed", rsp.getBackupStorageMetaFileName());
trigger.fail(ec);
} else {
logger.info("create image metadata file successfully");
SftpBackupStorageDumpMetadataInfo dumpInfo = new SftpBackupStorageDumpMetadataInfo();
dumpInfo.setImg(img);
dumpInfo.setDumpAllInfo(true);
dumpImagesBackupStorageInfoToMetaDataFile(dumpInfo);
trigger.next();
}
}
@Override
public Class<SftpBackupStorageCommands.GenerateImageMetaDataFileRsp> getReturnClass() {
return SftpBackupStorageCommands.GenerateImageMetaDataFileRsp.class;
}
});
} else {
SftpBackupStorageDumpMetadataInfo dumpInfo = new SftpBackupStorageDumpMetadataInfo();
dumpInfo.setDumpAllInfo(false);
dumpInfo.setImg(img);
dumpImagesBackupStorageInfoToMetaDataFile(dumpInfo);
trigger.next();
}
}
});
done(new FlowDoneHandler(null) {
@Override
public void handle(Map data) {
// do nothing
}
});
}
}).start();
}
@Override
public void failedToAddImage(ImageInventory img, ErrorCode err) {
}
@Override
public void preAddBackupStorage(AddBackupStorageStruct backupStorage) {
}
@Override
public void beforeAddBackupStorage(AddBackupStorageStruct backupStorage) {
}
@Override
public void afterAddBackupStorage(AddBackupStorageStruct backupStorage) {
String backupStorageType = backupStorage.getBackupStorageInventory().getType();
if (!( backupStorageType.equals(SftpBackupStorageConstant.SFTP_BACKUP_STORAGE_TYPE) && backupStorage.isImportImages())) {
return;
}
SftpBackupStorageInventory inv = (SftpBackupStorageInventory) backupStorage.getBackupStorageInventory();
logger.debug("starting to import images metadata");
SftpBackupStorageCommands.GetImagesMetaDataCmd cmd = new SftpBackupStorageCommands.GetImagesMetaDataCmd();
cmd.setBackupStoragePath(inv.getUrl());
cmd.uuid = inv.getUuid();
restf.asyncJsonPost(buildUrl(SftpBackupStorageConstant.GET_IMAGES_METADATA, inv.getHostname()), cmd,
new JsonAsyncRESTCallback<SftpBackupStorageCommands.GetImagesMetaDataRsp>(null) {
@Override
public void fail(ErrorCode err) {
logger.error("check image metadata file exist failed" + err.toString());
}
@Override
public void success(SftpBackupStorageCommands.GetImagesMetaDataRsp rsp) {
if (!rsp.isSuccess()) {
logger.error(String.format("get images metadata: %s failed", rsp.getImagesMetaData()));
} else {
logger.info(String.format("get images metadata: %s success", rsp.getImagesMetaData()));
restoreImagesBackupStorageMetadataToDatabase(rsp.getImagesMetaData(), backupStorage.getBackupStorageInventory().getUuid());
}
}
@Override
public Class<SftpBackupStorageCommands.GetImagesMetaDataRsp> getReturnClass() {
return SftpBackupStorageCommands.GetImagesMetaDataRsp.class;
}
});
}
public void failedToAddBackupStorage(AddBackupStorageStruct backupStorage, ErrorCode err) {
}
public void preExpungeImage(ImageInventory img) {
}
public void beforeExpungeImage(ImageInventory img) {
}
public void afterExpungeImage(ImageInventory img, String backupStorageUuid) {
if (!getBackupStorageTypeFromImageInventory(img).equals(SftpBackupStorageConstant.SFTP_BACKUP_STORAGE_TYPE)) {
return;
}
FlowChain chain = FlowChainBuilder.newShareFlowChain();
chain.setName("delete-image-info-from-metadata-file");
String hostName = getHostNameFromImageInventory(img);
String bsUrl = getBsUrlFromImageInventory(img);
chain.then(new ShareFlow() {
boolean metaDataExist = false;
@Override
public void setup() {
flow(new NoRollbackFlow() {
String __name__ = "check-image-metadata-file-exist";
@Override
public void run(FlowTrigger trigger, Map data) {
SftpBackupStorageCommands.CheckImageMetaDataFileExistCmd cmd = new SftpBackupStorageCommands.CheckImageMetaDataFileExistCmd();
cmd.setBackupStoragePath(bsUrl);
cmd.uuid = backupStorageUuid;
restf.asyncJsonPost(buildUrl(SftpBackupStorageConstant.CHECK_IMAGE_METADATA_FILE_EXIST, hostName), cmd,
new JsonAsyncRESTCallback<SftpBackupStorageCommands.CheckImageMetaDataFileExistRsp>(trigger) {
@Override
public void fail(ErrorCode err) {
logger.error("check image metadata file exist failed" + err.toString());
trigger.fail(err);
}
@Override
public void success(SftpBackupStorageCommands.CheckImageMetaDataFileExistRsp rsp) {
if (!rsp.isSuccess()) {
logger.error(String.format("check image metadata file: %s failed", rsp.getBackupStorageMetaFileName()));
ErrorCode ec = operr("check image metadata file: %s failed", rsp.getBackupStorageMetaFileName());
trigger.fail(ec);
} else {
if (!rsp.getExist()) {
logger.info(String.format("image metadata file %s is not exist", rsp.getBackupStorageMetaFileName()));
ErrorCode ec = operr("image metadata file: %s is not exist", rsp.getBackupStorageMetaFileName());
trigger.fail(ec);
} else {
logger.info(String.format("image metadata file %s exist", rsp.getBackupStorageMetaFileName()));
trigger.next();
}
}
}
@Override
public Class<SftpBackupStorageCommands.CheckImageMetaDataFileExistRsp> getReturnClass() {
return SftpBackupStorageCommands.CheckImageMetaDataFileExistRsp.class;
}
});
}
});
flow(new NoRollbackFlow() {
String __name__ = "delete-image-info";
@Override
public void run(FlowTrigger trigger, Map data) {
SftpBackupStorageCommands.DeleteImageInfoFromMetaDataFileCmd deleteCmd = new SftpBackupStorageCommands.DeleteImageInfoFromMetaDataFileCmd();
deleteCmd.setImageUuid(img.getUuid());
deleteCmd.setImageBackupStorageUuid(backupStorageUuid);
deleteCmd.setBackupStoragePath(bsUrl);
restf.asyncJsonPost(buildUrl(SftpBackupStorageConstant.DELETE_IMAGES_METADATA, hostName), deleteCmd,
new JsonAsyncRESTCallback<SftpBackupStorageCommands.DeleteImageInfoFromMetaDataFileRsp>(trigger) {
@Override
public void fail(ErrorCode err) {
logger.error("delete image metadata file failed" + err.toString());
}
@Override
public void success(SftpBackupStorageCommands.DeleteImageInfoFromMetaDataFileRsp rsp) {
if (!rsp.isSuccess()) {
ErrorCode ec = operr("delete image metadata file failed: %s", rsp.getError());
trigger.fail(ec);
} else {
if (rsp.getRet() != 0) {
logger.info(String.format("delete image %s metadata failed : %s", img.getUuid(), rsp.getOut()));
trigger.next();
} else {
logger.info(String.format("delete image %s metadata successfully", img.getUuid()));
trigger.next();
}
}
}
@Override
public Class<SftpBackupStorageCommands.DeleteImageInfoFromMetaDataFileRsp> getReturnClass() {
return SftpBackupStorageCommands.DeleteImageInfoFromMetaDataFileRsp.class;
}
});
}
});
done(new FlowDoneHandler(null) {
@Override
public void handle(Map data) {
// do nothing
}
});
}
}).start();
}
public void failedToExpungeImage(ImageInventory img, ErrorCode err) {
}
}