Skip to content

Commit 62ed673

Browse files
author
Mike Tutkowski
committed
Support online migration of a virtual disk on XenServer from non-managed storage to managed storage
1 parent efcd24c commit 62ed673

13 files changed

Lines changed: 1045 additions & 150 deletions

File tree

core/src/main/java/com/cloud/agent/api/storage/MigrateVolumeCommand.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,18 @@ public DataTO getDestData() {
9797
return destData;
9898
}
9999

100+
public void setSrcDetails(Map<String, String> details) {
101+
srcDetails = details;
102+
}
103+
100104
public Map<String, String> getSrcDetails() {
101105
return srcDetails;
102106
}
103107

108+
public void setDestDetails(Map<String, String> details) {
109+
destDetails = details;
110+
}
111+
104112
public Map<String, String> getDestDetails() {
105113
return destDetails;
106114
}

engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@
2525
import com.cloud.storage.StoragePool;
2626

2727
public interface PrimaryDataStoreDriver extends DataStoreDriver {
28+
enum QualityOfServiceState { MIGRATION, NO_MIGRATION }
29+
2830
String BASIC_CREATE = "basicCreate";
2931
String BASIC_DELETE = "basicDelete";
3032
String BASIC_DELETE_FAILURE = "basicDeleteFailure";
33+
String BASIC_DELETE_BY_FOLDER = "basicDeleteByFolder";
3134
String BASIC_GRANT_ACCESS = "basicGrantAccess";
3235
String BASIC_REVOKE_ACCESS = "basicRevokeAccess";
3336
String BASIC_IQN = "basicIqn";
@@ -67,4 +70,6 @@ public interface PrimaryDataStoreDriver extends DataStoreDriver {
6770
void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCmdResult> callback);
6871

6972
void revertSnapshot(SnapshotInfo snapshotOnImageStore, SnapshotInfo snapshotOnPrimaryStore, AsyncCompletionCallback<CommandResult> callback);
73+
74+
void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, QualityOfServiceState qualityOfServiceState);
7075
}

engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java

Lines changed: 402 additions & 101 deletions
Large diffs are not rendered by default.

plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java

Lines changed: 120 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1475,67 +1475,114 @@ public Answer createTemplateFromSnapshot(final CopyCommand cmd) {
14751475
}
14761476
}
14771477

1478+
private boolean isManaged(Map<String, String> options) {
1479+
if (options == null) {
1480+
return false;
1481+
}
1482+
1483+
String iqn = options.get(DiskTO.IQN);
1484+
1485+
if (iqn == null || iqn.trim().length() == 0) {
1486+
return false;
1487+
}
1488+
1489+
String storageHost = options.get(DiskTO.STORAGE_HOST);
1490+
1491+
if (storageHost == null || storageHost.trim().length() == 0) {
1492+
return false;
1493+
}
1494+
1495+
return true;
1496+
}
1497+
1498+
boolean isCreateManagedVolumeFromManagedSnapshot(Map<String, String> volumeOptions, Map<String, String> snapshotOptions) {
1499+
return isManaged(volumeOptions) && isManaged(snapshotOptions);
1500+
}
1501+
1502+
boolean isCreateNonManagedVolumeFromManagedSnapshot(Map<String, String> volumeOptions, Map<String, String> snapshotOptions) {
1503+
return !isManaged(volumeOptions) && isManaged(snapshotOptions);
1504+
}
1505+
14781506
@Override
14791507
public Answer createVolumeFromSnapshot(final CopyCommand cmd) {
1480-
final Connection conn = hypervisorResource.getConnection();
1481-
final DataTO srcData = cmd.getSrcTO();
1482-
final SnapshotObjectTO snapshot = (SnapshotObjectTO) srcData;
1483-
final DataTO destData = cmd.getDestTO();
1484-
final DataStoreTO imageStore = srcData.getDataStore();
1508+
Connection conn = hypervisorResource.getConnection();
1509+
1510+
DataTO srcData = cmd.getSrcTO();
1511+
SnapshotObjectTO snapshot = (SnapshotObjectTO)srcData;
1512+
DataStoreTO imageStore = srcData.getDataStore();
1513+
DataTO destData = cmd.getDestTO();
14851514

1486-
if (srcData.getDataStore() instanceof PrimaryDataStoreTO && destData.getDataStore() instanceof PrimaryDataStoreTO) {
1487-
return createVolumeFromSnapshot2(cmd);
1515+
if (isCreateManagedVolumeFromManagedSnapshot(cmd.getOptions2(), cmd.getOptions())) {
1516+
return createManagedVolumeFromManagedSnapshot(cmd);
1517+
}
1518+
1519+
if (isCreateNonManagedVolumeFromManagedSnapshot(cmd.getOptions2(), cmd.getOptions())) {
1520+
return createNonManagedVolumeFromManagedSnapshot(cmd);
14881521
}
14891522

14901523
if (!(imageStore instanceof NfsTO)) {
14911524
return new CopyCmdAnswer("unsupported protocol");
14921525
}
14931526

1494-
final NfsTO nfsImageStore = (NfsTO) imageStore;
1495-
final String primaryStorageNameLabel = destData.getDataStore().getUuid();
1496-
final String secondaryStorageUrl = nfsImageStore.getUrl();
1497-
final int wait = cmd.getWait();
1527+
NfsTO nfsImageStore = (NfsTO)imageStore;
1528+
String primaryStorageNameLabel = destData.getDataStore().getUuid();
1529+
String secondaryStorageUrl = nfsImageStore.getUrl();
1530+
1531+
int wait = cmd.getWait();
14981532
boolean result = false;
1533+
14991534
// Generic error message.
1500-
String details = null;
1501-
String volumeUUID = null;
1535+
String details;
1536+
String volumeUUID;
15021537

15031538
if (secondaryStorageUrl == null) {
1504-
details += " because the URL passed: " + secondaryStorageUrl + " is invalid.";
1539+
details = "The URL passed in 'null'.";
1540+
15051541
return new CopyCmdAnswer(details);
15061542
}
1543+
15071544
try {
1508-
final SR primaryStorageSR = hypervisorResource.getSRByNameLabelandHost(conn, primaryStorageNameLabel);
1545+
SR primaryStorageSR = hypervisorResource.getSRByNameLabelandHost(conn, primaryStorageNameLabel);
1546+
15091547
if (primaryStorageSR == null) {
1510-
throw new InternalErrorException("Could not create volume from snapshot because the primary Storage SR could not be created from the name label: " +
1511-
primaryStorageNameLabel);
1548+
throw new InternalErrorException("Could not create volume from snapshot because the primary storage SR could not be " +
1549+
"created from the name label: " + primaryStorageNameLabel);
15121550
}
1551+
15131552
// Get the absolute path of the snapshot on the secondary storage.
15141553
String snapshotInstallPath = snapshot.getPath();
1515-
final int index = snapshotInstallPath.lastIndexOf(nfsImageStore.getPathSeparator());
1516-
final String snapshotName = snapshotInstallPath.substring(index + 1);
1554+
int index = snapshotInstallPath.lastIndexOf(nfsImageStore.getPathSeparator());
1555+
String snapshotName = snapshotInstallPath.substring(index + 1);
15171556

15181557
if (!snapshotName.startsWith("VHD-") && !snapshotName.endsWith(".vhd")) {
15191558
snapshotInstallPath = snapshotInstallPath + ".vhd";
15201559
}
1521-
final URI snapshotURI = new URI(secondaryStorageUrl + nfsImageStore.getPathSeparator() + snapshotInstallPath);
1522-
final String snapshotPath = snapshotURI.getHost() + ":" + snapshotURI.getPath();
1523-
final String srUuid = primaryStorageSR.getUuid(conn);
1560+
1561+
URI snapshotURI = new URI(secondaryStorageUrl + nfsImageStore.getPathSeparator() + snapshotInstallPath);
1562+
String snapshotPath = snapshotURI.getHost() + ":" + snapshotURI.getPath();
1563+
String srUuid = primaryStorageSR.getUuid(conn);
1564+
15241565
volumeUUID = copy_vhd_from_secondarystorage(conn, snapshotPath, srUuid, wait);
15251566
result = true;
1526-
final VDI volume = VDI.getByUuid(conn, volumeUUID);
1527-
final VDI.Record vdir = volume.getRecord(conn);
1528-
final VolumeObjectTO newVol = new VolumeObjectTO();
1567+
1568+
VDI volume = VDI.getByUuid(conn, volumeUUID);
1569+
VDI.Record vdir = volume.getRecord(conn);
1570+
VolumeObjectTO newVol = new VolumeObjectTO();
1571+
15291572
newVol.setPath(volumeUUID);
15301573
newVol.setSize(vdir.virtualSize);
1574+
15311575
return new CopyCmdAnswer(newVol);
15321576
} catch (final XenAPIException e) {
1533-
details += " due to " + e.toString();
1577+
details = "Exception due to " + e.toString();
1578+
15341579
s_logger.warn(details, e);
15351580
} catch (final Exception e) {
1536-
details += " due to " + e.getMessage();
1581+
details = "Exception due to " + e.getMessage();
1582+
15371583
s_logger.warn(details, e);
15381584
}
1585+
15391586
if (!result) {
15401587
// Is this logged at a higher level?
15411588
s_logger.error(details);
@@ -1545,7 +1592,7 @@ public Answer createVolumeFromSnapshot(final CopyCommand cmd) {
15451592
return new CopyCmdAnswer(details);
15461593
}
15471594

1548-
protected Answer createVolumeFromSnapshot2(final CopyCommand cmd) {
1595+
Answer createManagedVolumeFromManagedSnapshot(final CopyCommand cmd) {
15491596
try {
15501597
final Connection conn = hypervisorResource.getConnection();
15511598

@@ -1590,6 +1637,51 @@ protected Answer createVolumeFromSnapshot2(final CopyCommand cmd) {
15901637
}
15911638
}
15921639

1640+
Answer createNonManagedVolumeFromManagedSnapshot(final CopyCommand cmd) {
1641+
Connection conn = hypervisorResource.getConnection();
1642+
SR srcSr = null;
1643+
1644+
try {
1645+
Map<String, String> srcOptions = cmd.getOptions();
1646+
1647+
String src_iScsiName = srcOptions.get(DiskTO.IQN);
1648+
String srcStorageHost = srcOptions.get(DiskTO.STORAGE_HOST);
1649+
String srcChapInitiatorUsername = srcOptions.get(DiskTO.CHAP_INITIATOR_USERNAME);
1650+
String srcChapInitiatorSecret = srcOptions.get(DiskTO.CHAP_INITIATOR_SECRET);
1651+
1652+
srcSr = hypervisorResource.getIscsiSR(conn, src_iScsiName, srcStorageHost, src_iScsiName,
1653+
srcChapInitiatorUsername, srcChapInitiatorSecret, false);
1654+
1655+
// there should only be one VDI in this SR
1656+
VDI srcVdi = srcSr.getVDIs(conn).iterator().next();
1657+
1658+
DataTO destData = cmd.getDestTO();
1659+
String primaryStorageNameLabel = destData.getDataStore().getUuid();
1660+
1661+
SR destSr = hypervisorResource.getSRByNameLabelandHost(conn, primaryStorageNameLabel);
1662+
1663+
VDI vdiCopy = srcVdi.copy(conn, destSr);
1664+
1665+
VolumeObjectTO newVol = new VolumeObjectTO();
1666+
1667+
newVol.setSize(vdiCopy.getVirtualSize(conn));
1668+
newVol.setPath(vdiCopy.getUuid(conn));
1669+
newVol.setFormat(ImageFormat.VHD);
1670+
1671+
return new CopyCmdAnswer(newVol);
1672+
}
1673+
catch (Exception ex) {
1674+
s_logger.warn("Failed to copy snapshot to volume: " + ex.toString(), ex);
1675+
1676+
return new CopyCmdAnswer(ex.getMessage());
1677+
}
1678+
finally {
1679+
if (srcSr != null) {
1680+
hypervisorResource.removeSR(conn, srcSr);
1681+
}
1682+
}
1683+
}
1684+
15931685
@Override
15941686
public Answer deleteSnapshot(final DeleteCommand cmd) {
15951687
final SnapshotObjectTO snapshot = (SnapshotObjectTO) cmd.getData();

plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/Xenserver625StorageProcessor.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -787,8 +787,12 @@ public Answer createVolumeFromSnapshot(final CopyCommand cmd) {
787787
final VolumeObjectTO volume = (VolumeObjectTO)destData;
788788
final DataStoreTO imageStore = srcData.getDataStore();
789789

790-
if (srcData.getDataStore() instanceof PrimaryDataStoreTO && destData.getDataStore() instanceof PrimaryDataStoreTO) {
791-
return createVolumeFromSnapshot2(cmd);
790+
if (isCreateManagedVolumeFromManagedSnapshot(cmd.getOptions2(), cmd.getOptions())) {
791+
return createManagedVolumeFromManagedSnapshot(cmd);
792+
}
793+
794+
if (isCreateNonManagedVolumeFromManagedSnapshot(cmd.getOptions2(), cmd.getOptions())) {
795+
return createNonManagedVolumeFromManagedSnapshot(cmd);
792796
}
793797

794798
if (!(imageStore instanceof NfsTO)) {

plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xen610/XenServer610MigrateVolumeCommandWrapper.java

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.cloud.agent.api.Answer;
2828
import com.cloud.agent.api.storage.MigrateVolumeAnswer;
2929
import com.cloud.agent.api.storage.MigrateVolumeCommand;
30+
import com.cloud.agent.api.to.DiskTO;
3031
import com.cloud.agent.api.to.StorageFilerTO;
3132
import com.cloud.hypervisor.xenserver.resource.XenServer610Resource;
3233
import com.cloud.resource.CommandWrapper;
@@ -39,35 +40,59 @@
3940

4041
@ResourceWrapper(handles = MigrateVolumeCommand.class)
4142
public final class XenServer610MigrateVolumeCommandWrapper extends CommandWrapper<MigrateVolumeCommand, Answer, XenServer610Resource> {
42-
43-
private static final Logger s_logger = Logger.getLogger(XenServer610MigrateVolumeCommandWrapper.class);
43+
private static final Logger LOGGER = Logger.getLogger(XenServer610MigrateVolumeCommandWrapper.class);
4444

4545
@Override
4646
public Answer execute(final MigrateVolumeCommand command, final XenServer610Resource xenServer610Resource) {
47-
final Connection connection = xenServer610Resource.getConnection();
48-
final String volumeUUID = command.getVolumePath();
49-
final StorageFilerTO poolTO = command.getPool();
47+
Connection connection = xenServer610Resource.getConnection();
48+
String srcVolumeUuid = command.getVolumePath();
49+
SR destPool = null;
50+
Map<String, String> destDetails = command.getDestDetails();
5051

5152
try {
52-
final String uuid = poolTO.getUuid();
53-
final SR destinationPool = xenServer610Resource.getStorageRepository(connection, uuid);
54-
final VDI srcVolume = xenServer610Resource.getVDIbyUuid(connection, volumeUUID);
55-
final Map<String, String> other = new HashMap<String, String>();
53+
VDI srcVolume = xenServer610Resource.getVDIbyUuid(connection, srcVolumeUuid);
54+
55+
if (destDetails != null && Boolean.parseBoolean(destDetails.get(DiskTO.MANAGED))) {
56+
String iScsiName = destDetails.get(DiskTO.IQN);
57+
String storageHost = destDetails.get(DiskTO.STORAGE_HOST);
58+
String chapInitiatorUsername = destDetails.get(DiskTO.CHAP_INITIATOR_USERNAME);
59+
String chapInitiatorSecret = destDetails.get(DiskTO.CHAP_INITIATOR_SECRET);
60+
61+
destPool = xenServer610Resource.getIscsiSR(connection, iScsiName, storageHost, iScsiName,
62+
chapInitiatorUsername, chapInitiatorSecret, false);
63+
}
64+
else {
65+
StorageFilerTO destPoolTO = command.getPool();
66+
String destPoolUuid = destPoolTO.getUuid();
67+
68+
destPool = xenServer610Resource.getStorageRepository(connection, destPoolUuid);
69+
}
70+
71+
Map<String, String> other = new HashMap<>();
72+
5673
other.put("live", "true");
5774

58-
// Live migrate the vdi across pool.
59-
final Task task = srcVolume.poolMigrateAsync(connection, destinationPool, other);
60-
final long timeout = xenServer610Resource.getMigrateWait() * 1000L;
75+
// Live migrate the VDI.
76+
Task task = srcVolume.poolMigrateAsync(connection, destPool, other);
77+
78+
long timeout = xenServer610Resource.getMigrateWait() * 1000L;
79+
6180
xenServer610Resource.waitForTask(connection, task, 1000, timeout);
6281
xenServer610Resource.checkForSuccess(connection, task);
6382

64-
final VDI dvdi = Types.toVDI(task, connection);
83+
VDI destVdi = Types.toVDI(task, connection);
84+
85+
return new MigrateVolumeAnswer(command, true, null, destVdi.getUuid(connection));
86+
} catch (Exception ex) {
87+
if (destDetails != null && Boolean.parseBoolean(destDetails.get(DiskTO.MANAGED)) && destPool != null) {
88+
xenServer610Resource.removeSR(connection, destPool);
89+
}
90+
91+
String msg = "Caught exception " + ex.getClass().getName() + " due to the following: " + ex.toString();
92+
93+
LOGGER.error(msg, ex);
6594

66-
return new MigrateVolumeAnswer(command, true, null, dvdi.getUuid(connection));
67-
} catch (final Exception e) {
68-
final String msg = "Catch Exception " + e.getClass().getName() + " due to " + e.toString();
69-
s_logger.error(msg, e);
7095
return new MigrateVolumeAnswer(command, false, msg, null);
7196
}
7297
}
73-
}
98+
}

plugins/storage/volume/cloudbyte/src/main/java/org/apache/cloudstack/storage/datastore/driver/ElastistorPrimaryDataStoreDriver.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,9 @@ public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> cal
321321

322322
}
323323

324+
@Override
325+
public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, QualityOfServiceState qualityOfServiceState) {}
326+
324327
//this method will utilize the volume details table to add third party volume properties
325328
public void updateVolumeDetails(VolumeVO volume, FileSystem esvolume) {
326329

plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,4 +385,7 @@ public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> cal
385385

386386
callback.complete(result);
387387
}
388+
389+
@Override
390+
public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, QualityOfServiceState qualityOfServiceState) {}
388391
}

plugins/storage/volume/nexenta/src/main/java/org/apache/cloudstack/storage/datastore/driver/NexentaPrimaryDataStoreDriver.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,7 @@ public boolean canCopy(DataObject srcData, DataObject destData) {
206206

207207
@Override
208208
public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {}
209+
210+
@Override
211+
public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, QualityOfServiceState qualityOfServiceState) {}
209212
}

plugins/storage/volume/sample/src/main/java/org/apache/cloudstack/storage/datastore/driver/SamplePrimaryDataStoreDriverImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
3333
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
3434
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
35+
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
3536
import org.apache.cloudstack.framework.async.AsyncCallbackDispatcher;
3637
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
3738
import org.apache.cloudstack.framework.async.AsyncRpcContext;
@@ -227,6 +228,10 @@ public void copyAsync(DataObject srcdata, DataObject destData, AsyncCompletionCa
227228
public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {
228229
}
229230

231+
@Override
232+
public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, QualityOfServiceState qualityOfServiceState) {
233+
}
234+
230235
@Override
231236
public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCmdResult> callback) {
232237
}

0 commit comments

Comments
 (0)