Skip to content

Commit e9ff270

Browse files
committed
Merge branch '4.19'
2 parents 0d1bc7d + 87e7c57 commit e9ff270

File tree

2 files changed

+64
-4
lines changed

2 files changed

+64
-4
lines changed

plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
import org.apache.cloudstack.storage.datastore.util.LinstorConfigurationManager;
103103
import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
104104
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
105+
import org.apache.cloudstack.storage.to.VolumeObjectTO;
105106
import org.apache.cloudstack.storage.volume.VolumeObject;
106107
import org.apache.logging.log4j.Logger;
107108
import org.apache.logging.log4j.LogManager;
@@ -798,6 +799,15 @@ private static boolean canCopyTemplateCond(DataObject srcData, DataObject dstDat
798799
|| srcData.getDataStore().getRole() == DataStoreRole.ImageCache);
799800
}
800801

802+
private static boolean canCopyVolumeCond(DataObject srcData, DataObject dstData) {
803+
// Volume download from Linstor primary storage
804+
return srcData.getType() == DataObjectType.VOLUME
805+
&& (dstData.getType() == DataObjectType.VOLUME || dstData.getType() == DataObjectType.TEMPLATE)
806+
&& srcData.getDataStore().getRole() == DataStoreRole.Primary
807+
&& (dstData.getDataStore().getRole() == DataStoreRole.Image
808+
|| dstData.getDataStore().getRole() == DataStoreRole.ImageCache);
809+
}
810+
801811
@Override
802812
public boolean canCopy(DataObject srcData, DataObject dstData)
803813
{
@@ -814,6 +824,10 @@ public boolean canCopy(DataObject srcData, DataObject dstData)
814824
return storagePoolVO != null
815825
&& storagePoolVO.getPoolType() == Storage.StoragePoolType.Linstor
816826
&& tInfo.getSize() != null;
827+
} else if (canCopyVolumeCond(srcData, dstData)) {
828+
VolumeInfo srcVolInfo = (VolumeInfo) srcData;
829+
StoragePoolVO storagePool = _storagePoolDao.findById(srcVolInfo.getPoolId());
830+
return storagePool.getStorageProviderName().equals(LinstorUtil.PROVIDER_NAME);
817831
}
818832
return false;
819833
}
@@ -844,6 +858,9 @@ public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCal
844858
} else if (canCopyTemplateCond(srcData, dstData)) {
845859
Answer answer = copyTemplate(srcData, dstData);
846860
res = new CopyCommandResult(null, answer);
861+
} else if (canCopyVolumeCond(srcData, dstData)) {
862+
Answer answer = copyVolume(srcData, dstData);
863+
res = new CopyCommandResult(null, answer);
847864
} else {
848865
Answer answer = new Answer(null, false, "noimpl");
849866
res = new CopyCommandResult(null, answer);
@@ -965,6 +982,43 @@ private Answer copyTemplate(DataObject srcData, DataObject dstData) {
965982
return answer;
966983
}
967984

985+
private Answer copyVolume(DataObject srcData, DataObject dstData) {
986+
VolumeInfo srcVolInfo = (VolumeInfo) srcData;
987+
final StoragePoolVO pool = _storagePoolDao.findById(srcVolInfo.getDataStore().getId());
988+
final DevelopersApi api = LinstorUtil.getLinstorAPI(pool.getHostAddress());
989+
final String rscName = LinstorUtil.RSC_PREFIX + srcVolInfo.getUuid();
990+
991+
VolumeObjectTO to = (VolumeObjectTO) srcVolInfo.getTO();
992+
// patch source format
993+
// Linstor volumes are stored as RAW, but we can't set the correct format as RAW (we use QCOW2)
994+
// otherwise create template from snapshot won't work, because this operation
995+
// uses the format of the base volume and we backup snapshots as QCOW2
996+
// https://github.com/apache/cloudstack/pull/8802#issuecomment-2024019927
997+
to.setFormat(Storage.ImageFormat.RAW);
998+
int nMaxExecutionSeconds = NumbersUtil.parseInt(
999+
_configDao.getValue(Config.CopyVolumeWait.key()), 10800);
1000+
CopyCommand cmd = new CopyCommand(
1001+
to,
1002+
dstData.getTO(),
1003+
nMaxExecutionSeconds,
1004+
VirtualMachineManager.ExecuteInSequence.value());
1005+
Answer answer;
1006+
1007+
try {
1008+
Optional<RemoteHostEndPoint> optEP = getLinstorEP(api, rscName);
1009+
if (optEP.isPresent()) {
1010+
answer = optEP.get().sendMessage(cmd);
1011+
}
1012+
else {
1013+
answer = new Answer(cmd, false, "Unable to get matching Linstor endpoint.");
1014+
}
1015+
} catch (ApiException exc) {
1016+
logger.error("copy volume failed: ", exc);
1017+
throw new CloudRuntimeException(exc.getBestMessage());
1018+
}
1019+
return answer;
1020+
}
1021+
9681022
/**
9691023
* Create a temporary resource from the snapshot to backup, so we can copy the data on a diskless agent
9701024
* @param api Linstor Developer api object

test/integration/smoke/test_restore_vm.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def setUpClass(cls):
3232
testClient = super(TestRestoreVM, cls).getClsTestClient()
3333
cls.apiclient = testClient.getApiClient()
3434
cls.services = testClient.getParsedTestDataConfig()
35+
cls._cleanup = []
3536

3637
# Get Zone, Domain and templates
3738
cls.domain = get_domain(cls.apiclient)
@@ -42,16 +43,21 @@ def setUpClass(cls):
4243
cls.services["virtual_machine"]["zoneid"] = cls.zone.id
4344

4445
cls.service_offering = ServiceOffering.create(cls.apiclient, cls.services["service_offering"])
46+
cls._cleanup.append(cls.service_offering)
4547

46-
cls.template_t1 = Template.register(cls.apiclient, cls.services["test_templates"][
48+
template_t1 = Template.register(cls.apiclient, cls.services["test_templates"][
4749
cls.hypervisor.lower() if cls.hypervisor.lower() != 'simulator' else 'xenserver'],
4850
zoneid=cls.zone.id, hypervisor=cls.hypervisor.lower())
51+
cls._cleanup.append(template_t1)
52+
template_t1.download(cls.apiclient)
53+
cls.template_t1 = Template.list(cls.apiclient, templatefilter='all', id=template_t1.id)[0]
4954

50-
cls.template_t2 = Template.register(cls.apiclient, cls.services["test_templates"][
55+
template_t2 = Template.register(cls.apiclient, cls.services["test_templates"][
5156
cls.hypervisor.lower() if cls.hypervisor.lower() != 'simulator' else 'xenserver'],
5257
zoneid=cls.zone.id, hypervisor=cls.hypervisor.lower())
53-
54-
cls._cleanup = [cls.service_offering, cls.template_t1, cls.template_t2]
58+
cls._cleanup.append(template_t2)
59+
template_t2.download(cls.apiclient)
60+
cls.template_t2 = Template.list(cls.apiclient, templatefilter='all', id=template_t2.id)[0]
5561

5662
@classmethod
5763
def tearDownClass(cls):

0 commit comments

Comments
 (0)