diff --git a/core/src/org/apache/cloudstack/storage/to/TemplateObjectTO.java b/core/src/org/apache/cloudstack/storage/to/TemplateObjectTO.java index aa854013c59..9ee90b72ddd 100644 --- a/core/src/org/apache/cloudstack/storage/to/TemplateObjectTO.java +++ b/core/src/org/apache/cloudstack/storage/to/TemplateObjectTO.java @@ -135,6 +135,10 @@ public class TemplateObjectTO implements DataTO { return this.imageDataStore; } + public void setHypervisorType(Hypervisor.HypervisorType hypervisorType) { + this.hypervisorType = hypervisorType; + } + @Override public Hypervisor.HypervisorType getHypervisorType() { return this.hypervisorType; diff --git a/engine/storage/datamotion/resources/META-INF/cloudstack/storage/spring-engine-storage-datamotion-storage-context.xml b/engine/storage/datamotion/resources/META-INF/cloudstack/storage/spring-engine-storage-datamotion-storage-context.xml index ade22dbc25b..fb69da57a6d 100644 --- a/engine/storage/datamotion/resources/META-INF/cloudstack/storage/spring-engine-storage-datamotion-storage-context.xml +++ b/engine/storage/datamotion/resources/META-INF/cloudstack/storage/spring-engine-storage-datamotion-storage-context.xml @@ -32,4 +32,6 @@ class="org.apache.cloudstack.storage.motion.XenServerStorageMotionStrategy" /> + diff --git a/engine/storage/datamotion/src/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java b/engine/storage/datamotion/src/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java index d9150634d95..cf9e9dc11a9 100644 --- a/engine/storage/datamotion/src/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java +++ b/engine/storage/datamotion/src/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java @@ -18,8 +18,12 @@ */ package org.apache.cloudstack.storage.motion; +import java.util.HashMap; +import java.util.List; import java.util.Map; +import javax.inject.Inject; + import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -27,20 +31,68 @@ import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionStrategy; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService; import org.apache.cloudstack.framework.async.AsyncCompletionCallback; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.storage.command.CopyCommand; +import org.apache.cloudstack.storage.command.CopyCmdAnswer; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.to.DiskTO; import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.configuration.Config; import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.org.Cluster; +import com.cloud.org.Grouping.AllocationState; +import com.cloud.resource.ResourceState; +import com.cloud.server.ManagementService; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.dao.SnapshotDetailsDao; +import com.cloud.storage.dao.SnapshotDetailsVO; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachineManager; @Component public class StorageSystemDataMotionStrategy implements DataMotionStrategy { private static final Logger s_logger = Logger.getLogger(StorageSystemDataMotionStrategy.class); + @Inject private AgentManager _agentMgr; + @Inject private ConfigurationDao _configDao; + @Inject private HostDao _hostDao; + @Inject private ManagementService _mgr; + @Inject private PrimaryDataStoreDao _storagePoolDao; + @Inject private SnapshotDetailsDao _snapshotDetailsDao; + @Inject private VolumeService _volService; + @Override public StrategyPriority canHandle(DataObject srcData, DataObject destData) { - return StrategyPriority.DEFAULT; + if (srcData instanceof SnapshotInfo && destData.getDataStore().getRole() == DataStoreRole.Image || destData.getDataStore().getRole() == DataStoreRole.ImageCache) { + DataStore dataStore = srcData.getDataStore(); + Map mapCapabilities = dataStore.getDriver().getCapabilities(); + + if (mapCapabilities != null) { + String value = mapCapabilities.get(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString()); + Boolean supportsStorageSystemSnapshots = new Boolean(value); + + if (supportsStorageSystemSnapshots) { + s_logger.info("Using 'StorageSystemDataMotionStrategy'"); + + return StrategyPriority.HIGHEST; + } + } + } + + return StrategyPriority.CANT_HANDLE; } @Override @@ -50,10 +102,114 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy { @Override public Void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback callback) { + if (srcData instanceof SnapshotInfo && destData.getDataStore().getRole() == DataStoreRole.Image || destData.getDataStore().getRole() == DataStoreRole.ImageCache) { + SnapshotInfo snapshotInfo = (SnapshotInfo)srcData; + HostVO hostVO = getHost(srcData); + DataStore srcDataStore = srcData.getDataStore(); + + String value = _configDao.getValue(Config.PrimaryStorageDownloadWait.toString()); + int primaryStorageDownloadWait = NumbersUtil.parseInt(value, Integer.parseInt(Config.PrimaryStorageDownloadWait.getDefaultValue())); + CopyCommand copyCommand = new CopyCommand(srcData.getTO(), destData.getTO(), primaryStorageDownloadWait, VirtualMachineManager.ExecuteInSequence.value()); + + CopyCmdAnswer copyCmdAnswer = null; + + try { + _volService.grantAccess(snapshotInfo, hostVO, srcDataStore); + + Map srcDetails = getSourceDetails(_storagePoolDao.findById(srcDataStore.getId()), snapshotInfo); + + copyCommand.setOptions(srcDetails); + + copyCmdAnswer = (CopyCmdAnswer)_agentMgr.send(hostVO.getId(), copyCommand); + } + catch (Exception ex) { + throw new CloudRuntimeException(ex.getMessage()); + } + finally { + try { + _volService.revokeAccess(snapshotInfo, hostVO, srcDataStore); + } + catch (Exception ex) { + s_logger.debug(ex.getMessage(), ex); + } + } + + String errMsg = null; + + if (copyCmdAnswer == null || !copyCmdAnswer.getResult()) { + if (copyCmdAnswer != null && copyCmdAnswer.getDetails() != null && !copyCmdAnswer.getDetails().isEmpty()) { + errMsg = copyCmdAnswer.getDetails(); + } + else { + errMsg = "Unable to perform host-side operation"; + } + } + + CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer); + + result.setResult(errMsg); + + callback.complete(result); + } return null; } + private Map getSourceDetails(StoragePoolVO storagePoolVO, SnapshotInfo snapshotInfo) { + Map srcDetails = new HashMap(); + + srcDetails.put(DiskTO.STORAGE_HOST, storagePoolVO.getHostAddress()); + srcDetails.put(DiskTO.STORAGE_PORT, String.valueOf(storagePoolVO.getPort())); + + long snapshotId = snapshotInfo.getId(); + + srcDetails.put(DiskTO.IQN, getProperty(snapshotId, DiskTO.IQN)); + + srcDetails.put(DiskTO.CHAP_INITIATOR_USERNAME, getProperty(snapshotId, DiskTO.CHAP_INITIATOR_USERNAME)); + srcDetails.put(DiskTO.CHAP_INITIATOR_SECRET, getProperty(snapshotId, DiskTO.CHAP_INITIATOR_SECRET)); + srcDetails.put(DiskTO.CHAP_TARGET_USERNAME, getProperty(snapshotId, DiskTO.CHAP_TARGET_USERNAME)); + srcDetails.put(DiskTO.CHAP_TARGET_SECRET, getProperty(snapshotId, DiskTO.CHAP_TARGET_SECRET)); + + return srcDetails; + } + + private String getProperty(long snapshotId, String property) { + SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(snapshotId, property); + + if (snapshotDetails != null) { + return snapshotDetails.getValue(); + } + + return null; + } + + public HostVO getHost(DataObject srcData) { + long dataStoreId = srcData.getDataStore().getId(); + StoragePoolVO storagePoolVO = _storagePoolDao.findById(dataStoreId); + + List clusters = _mgr.searchForClusters(storagePoolVO.getDataCenterId(), new Long(0), Long.MAX_VALUE, HypervisorType.XenServer.toString()); + + if (clusters == null) { + throw new CloudRuntimeException("Unable to locate an applicable cluster"); + } + + for (Cluster cluster : clusters) { + if (cluster.getAllocationState() == AllocationState.Enabled) { + List hosts = _hostDao.findByClusterId(cluster.getId()); + + if (hosts != null) { + for (HostVO host : hosts) { + if (host.getResourceState() == ResourceState.Enabled) { + return host; + } + } + } + } + } + + throw new CloudRuntimeException("Unable to locate an applicable cluster"); + } + @Override public Void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback callback) { return copyAsync(srcData, destData, null, callback); diff --git a/engine/storage/image/src/org/apache/cloudstack/storage/image/store/TemplateObject.java b/engine/storage/image/src/org/apache/cloudstack/storage/image/store/TemplateObject.java index 7288d454c30..e4b04de9e9b 100644 --- a/engine/storage/image/src/org/apache/cloudstack/storage/image/store/TemplateObject.java +++ b/engine/storage/image/src/org/apache/cloudstack/storage/image/store/TemplateObject.java @@ -217,6 +217,9 @@ public class TemplateObject implements TemplateInfo { // For template created from snapshot, template name is determine by resource code. templateVO.setUniqueName(newTemplate.getName()); } + if (newTemplate.getHypervisorType() != null) { + templateVO.setHypervisorType(newTemplate.getHypervisorType()); + } templateVO.setSize(newTemplate.getSize()); imageDao.update(templateVO.getId(), templateVO); } diff --git a/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java b/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java index 7427f37c4cb..9a6c51b709e 100644 --- a/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java +++ b/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java @@ -226,7 +226,7 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase { sourceDetails = getSourceDetails(volumeInfo); } - HostVO hostVO = getHostId(hostId, volumeVO); + HostVO hostVO = getHost(hostId, volumeVO); long storagePoolId = volumeVO.getPoolId(); StoragePoolVO storagePoolVO = _storagePoolDao.findById(storagePoolId); @@ -340,7 +340,7 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase { return null; } - private HostVO getHostId(Long hostId, VolumeVO volumeVO) { + private HostVO getHost(Long hostId, VolumeVO volumeVO) { HostVO hostVO = _hostDao.findById(hostId); if (hostVO != null) { @@ -404,7 +404,8 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase { DataStore dataStore = _dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary); Map mapCapabilities = dataStore.getDriver().getCapabilities(); - if(mapCapabilities != null) { + + if (mapCapabilities != null) { String value = mapCapabilities.get(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString()); Boolean supportsStorageSystemSnapshots = new Boolean(value); @@ -412,6 +413,7 @@ public class StorageSystemSnapshotStrategy extends SnapshotStrategyBase { return StrategyPriority.HIGHEST; } } + return StrategyPriority.CANT_HANDLE; } } diff --git a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java index 813bf2bc424..f261410b1ee 100644 --- a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java +++ b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java @@ -64,8 +64,10 @@ import com.cloud.agent.api.to.S3TO; import com.cloud.agent.api.to.StorageFilerTO; import com.cloud.agent.api.to.SwiftTO; import com.cloud.exception.InternalErrorException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.xenserver.resource.CitrixResourceBase.SRType; import com.cloud.storage.DataStoreRole; +import com.cloud.storage.Storage; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.resource.StorageProcessor; import com.cloud.utils.S3Utils; @@ -1457,7 +1459,126 @@ public class XenServerStorageProcessor implements StorageProcessor { @Override public Answer createTemplateFromSnapshot(CopyCommand cmd) { - return null; //To change body of implemented methods use File | Settings | File Templates. + Connection conn = hypervisorResource.getConnection(); + + SnapshotObjectTO snapshotObjTO = (SnapshotObjectTO)cmd.getSrcTO(); + TemplateObjectTO templateObjTO = (TemplateObjectTO)cmd.getDestTO(); + + if (!(snapshotObjTO.getDataStore() instanceof PrimaryDataStoreTO) || !(templateObjTO.getDataStore() instanceof NfsTO)) { + return null; + } + + String userSpecifiedTemplateName = templateObjTO.getName(); + + NfsTO destStore = null; + URI destUri = null; + + try { + destStore = (NfsTO)templateObjTO.getDataStore(); + + destUri = new URI(destStore.getUrl()); + } catch (Exception ex) { + s_logger.debug("Invalid URI", ex); + + return new CopyCmdAnswer("Invalid URI: " + ex.toString()); + } + + SR srcSr = null; + SR destSr = null; + + String destDir = templateObjTO.getPath(); + VDI destVdi = null; + + boolean result = false; + + try { + Map srcDetails = cmd.getOptions(); + + String iScsiName = srcDetails.get(DiskTO.IQN); + String storageHost = srcDetails.get(DiskTO.STORAGE_HOST); + String chapInitiatorUsername = srcDetails.get(DiskTO.CHAP_INITIATOR_USERNAME); + String chapInitiatorSecret = srcDetails.get(DiskTO.CHAP_INITIATOR_SECRET); + + srcSr = hypervisorResource.getIscsiSR(conn, iScsiName, storageHost, iScsiName, chapInitiatorUsername, chapInitiatorSecret, true); + + String destNfsPath = destUri.getHost() + ":" + destUri.getPath(); + + if (!hypervisorResource.createSecondaryStorageFolder(conn, destNfsPath, destDir)) { + String details = " Failed to create folder " + destDir + " in secondary storage"; + + s_logger.warn(details); + + return new CopyCmdAnswer(details); + } + + URI templateUri = new URI(destStore.getUrl() + "/" + destDir); + + destSr = hypervisorResource.createNfsSRbyURI(conn, templateUri, false); + + // there should only be one VDI in this SR + VDI srcVdi = srcSr.getVDIs(conn).iterator().next(); + + destVdi = srcVdi.copy(conn, destSr); + + // scan makes XenServer pick up VDI physicalSize + destSr.scan(conn); + + if (userSpecifiedTemplateName != null) { + destVdi.setNameLabel(conn, userSpecifiedTemplateName); + } + + String templateUuid = destVdi.getUuid(conn); + String templateFilename = templateUuid + ".vhd"; + long virtualSize = destVdi.getVirtualSize(conn); + long physicalSize = destVdi.getPhysicalUtilisation(conn); + + // create the template.properties file + String templatePath = destNfsPath + "/" + destDir; + + templatePath = templatePath.replaceAll("//", "/"); + + result = hypervisorResource.postCreatePrivateTemplate(conn, templatePath, templateFilename, templateUuid, userSpecifiedTemplateName, null, + physicalSize, virtualSize, templateObjTO.getId()); + + if (!result) { + throw new CloudRuntimeException("Could not create the template.properties file on secondary storage dir: " + templateUri); + } + + TemplateObjectTO newTemplate = new TemplateObjectTO(); + + newTemplate.setPath(destDir + "/" + templateFilename); + newTemplate.setFormat(Storage.ImageFormat.VHD); + newTemplate.setHypervisorType(HypervisorType.XenServer); + newTemplate.setSize(virtualSize); + newTemplate.setPhysicalSize(physicalSize); + newTemplate.setName(templateUuid); + + result = true; + + return new CopyCmdAnswer(newTemplate); + } catch (Exception ex) { + s_logger.error("Failed to create a template from a snapshot", ex); + + return new CopyCmdAnswer("Failed to create a template from a snapshot: " + ex.toString()); + } finally { + if (!result) { + if (destVdi != null) { + try { + destVdi.destroy(conn); + } catch (Exception e) { + s_logger.debug("Cleaned up leftover VDI on destination storage due to failure: ", e); + } + } + } + + if (srcSr != null) { + hypervisorResource.removeSR(conn, srcSr); + } + + if (destSr != null) { + hypervisorResource.removeSR(conn, destSr); + } + } } @Override diff --git a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/Xenserver625StorageProcessor.java b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/Xenserver625StorageProcessor.java index 20baffd6bf4..7e2a097dd7d 100644 --- a/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/Xenserver625StorageProcessor.java +++ b/plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/Xenserver625StorageProcessor.java @@ -61,69 +61,94 @@ public class Xenserver625StorageProcessor extends XenServerStorageProcessor { public Xenserver625StorageProcessor(CitrixResourceBase resource) { super(resource); } + protected boolean mountNfs(Connection conn, String remoteDir, String localDir) { if (localDir == null) { localDir = "/var/cloud_mount/" + UUID.nameUUIDFromBytes(remoteDir.getBytes()); } + String results = hypervisorResource.callHostPluginAsync(conn, "cloud-plugin-storage", "mountNfsSecondaryStorage", 100 * 1000, "localDir", localDir, "remoteDir", remoteDir); + if (results == null || results.isEmpty()) { String errMsg = "Could not mount secondary storage " + remoteDir + " on host " + localDir; + s_logger.warn(errMsg); + throw new CloudRuntimeException(errMsg); } + return true; } protected boolean makeDirectory(Connection conn, String path) { String result = hypervisorResource.callHostPlugin(conn, "cloud-plugin-storage", "makeDirectory", "path", path); + if (result == null || result.isEmpty()) { return false; } + return true; } protected SR createFileSR(Connection conn, String path) { SR sr = null; PBD pbd = null; + try { String srname = hypervisorResource.getHost().uuid + path.trim(); + Set srs = SR.getByNameLabel(conn, srname); - if ( srs != null && !srs.isEmpty()) { + + if (srs != null && !srs.isEmpty()) { return srs.iterator().next(); } + Map smConfig = new HashMap(); + Host host = Host.getByUuid(conn, hypervisorResource.getHost().uuid); String uuid = UUID.randomUUID().toString(); sr = SR.introduce(conn, uuid, srname, srname, "file", "file", false, smConfig); + PBD.Record record = new PBD.Record(); + record.host = host; record.SR = sr; + smConfig.put("location", path); + record.deviceConfig = smConfig; + pbd = PBD.create(conn, record); + pbd.plug(conn); + sr.scan(conn); + return sr; - } catch (Exception e) { + } catch (Exception ex) { try { if (pbd != null) { pbd.destroy(conn); } } catch (Exception e1) { - s_logger.debug("Failed to destroy pbd", e); + s_logger.debug("Failed to destroy PBD", ex); } + try { if (sr != null) { sr.forget(conn); } } catch (Exception e2) { - s_logger.error("Failed to forget sr", e); + s_logger.error("Failed to forget SR", ex); } - String msg = "createFileSR failed! due to " + e.toString(); - s_logger.warn(msg, e); - throw new CloudRuntimeException(msg, e); + + String msg = "createFileSR failed! due to the following: " + ex.toString(); + + s_logger.warn(msg, ex); + + throw new CloudRuntimeException(msg, ex); } }