This is an automated email from the ASF dual-hosted git repository.
dahn pushed a commit to branch 4.19
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.19 by this push:
new 55e8eaab89c Linstor: encryption support (#10126)
55e8eaab89c is described below
commit 55e8eaab89cad7053b3bf0cd2d82808d70f8ef70
Author: Rene Peinthor <[email protected]>
AuthorDate: Tue Feb 4 15:18:49 2025 +0100
Linstor: encryption support (#10126)
This introduces a new encryption mode, instead of a simple bool.
Now also storage driver can just provide encrypted volumes to CloudStack.
---
api/src/main/java/com/cloud/storage/Storage.java | 65 +++++++----
.../kvm/resource/LibvirtComputingResource.java | 3 +-
.../kvm/storage/KVMStorageProcessor.java | 4 +-
plugins/storage/volume/linstor/CHANGELOG.md | 5 +
.../LinstorBackupSnapshotCommandWrapper.java | 18 ++-
.../kvm/storage/LinstorStorageAdaptor.java | 2 +-
.../driver/LinstorPrimaryDataStoreDriverImpl.java | 68 ++++++++++-
.../LinstorPrimaryDataStoreDriverImplTest.java | 87 ++++++++++++++
.../storage/datastore/util/LinstorUtilTest.java | 127 +++++++++++++++++++++
pom.xml | 2 +-
10 files changed, 346 insertions(+), 35 deletions(-)
diff --git a/api/src/main/java/com/cloud/storage/Storage.java
b/api/src/main/java/com/cloud/storage/Storage.java
index 1163fcc892f..0d7a3ed90c0 100644
--- a/api/src/main/java/com/cloud/storage/Storage.java
+++ b/api/src/main/java/com/cloud/storage/Storage.java
@@ -135,34 +135,49 @@ public class Storage {
ISODISK /* Template corresponding to a iso (non root disk) present in
an OVA */
}
+ public enum EncryptionSupport {
+ /**
+ * Encryption not supported.
+ */
+ Unsupported,
+ /**
+ * Will use hypervisor encryption driver (qemu -> luks)
+ */
+ Hypervisor,
+ /**
+ * Storage pool handles encryption and just provides an encrypted
volume
+ */
+ Storage
+ }
+
public static enum StoragePoolType {
- Filesystem(false, true, true), // local directory
- NetworkFilesystem(true, true, true), // NFS
- IscsiLUN(true, false, false), // shared LUN, with a clusterfs overlay
- Iscsi(true, false, false), // for e.g., ZFS Comstar
- ISO(false, false, false), // for iso image
- LVM(false, false, false), // XenServer local LVM SR
- CLVM(true, false, false),
- RBD(true, true, false), //
http://libvirt.org/storage.html#StorageBackendRBD
- SharedMountPoint(true, true, true),
- VMFS(true, true, false), // VMware VMFS storage
- PreSetup(true, true, false), // for XenServer, Storage Pool is set up
by customers.
- EXT(false, true, false), // XenServer local EXT SR
- OCFS2(true, false, false),
- SMB(true, false, false),
- Gluster(true, false, false),
- PowerFlex(true, true, true), // Dell EMC PowerFlex/ScaleIO (formerly
VxFlexOS)
- ManagedNFS(true, false, false),
- Linstor(true, true, false),
- DatastoreCluster(true, true, false), // for VMware, to abstract pool
of clusters
- StorPool(true, true, true),
- FiberChannel(true, true, false); // Fiber Channel Pool for KVM
hypervisors is used to find the volume by WWN value
(/dev/disk/by-id/wwn-<wwnvalue>)
+ Filesystem(false, true, EncryptionSupport.Hypervisor), // local
directory
+ NetworkFilesystem(true, true, EncryptionSupport.Hypervisor), // NFS
+ IscsiLUN(true, false, EncryptionSupport.Unsupported), // shared LUN,
with a clusterfs overlay
+ Iscsi(true, false, EncryptionSupport.Unsupported), // for e.g., ZFS
Comstar
+ ISO(false, false, EncryptionSupport.Unsupported), // for iso image
+ LVM(false, false, EncryptionSupport.Unsupported), // XenServer local
LVM SR
+ CLVM(true, false, EncryptionSupport.Unsupported),
+ RBD(true, true, EncryptionSupport.Unsupported), //
http://libvirt.org/storage.html#StorageBackendRBD
+ SharedMountPoint(true, true, EncryptionSupport.Hypervisor),
+ VMFS(true, true, EncryptionSupport.Unsupported), // VMware VMFS storage
+ PreSetup(true, true, EncryptionSupport.Unsupported), // for XenServer,
Storage Pool is set up by customers.
+ EXT(false, true, EncryptionSupport.Unsupported), // XenServer local
EXT SR
+ OCFS2(true, false, EncryptionSupport.Unsupported),
+ SMB(true, false, EncryptionSupport.Unsupported),
+ Gluster(true, false, EncryptionSupport.Unsupported),
+ PowerFlex(true, true, EncryptionSupport.Hypervisor), // Dell EMC
PowerFlex/ScaleIO (formerly VxFlexOS)
+ ManagedNFS(true, false, EncryptionSupport.Unsupported),
+ Linstor(true, true, EncryptionSupport.Storage),
+ DatastoreCluster(true, true, EncryptionSupport.Unsupported), // for
VMware, to abstract pool of clusters
+ StorPool(true, true, EncryptionSupport.Hypervisor),
+ FiberChannel(true, true, EncryptionSupport.Unsupported); // Fiber
Channel Pool for KVM hypervisors is used to find the volume by WWN value
(/dev/disk/by-id/wwn-<wwnvalue>)
private final boolean shared;
private final boolean overProvisioning;
- private final boolean encryption;
+ private final EncryptionSupport encryption;
- StoragePoolType(boolean shared, boolean overProvisioning, boolean
encryption) {
+ StoragePoolType(boolean shared, boolean overProvisioning,
EncryptionSupport encryption) {
this.shared = shared;
this.overProvisioning = overProvisioning;
this.encryption = encryption;
@@ -177,6 +192,10 @@ public class Storage {
}
public boolean supportsEncryption() {
+ return encryption == EncryptionSupport.Hypervisor || encryption ==
EncryptionSupport.Storage;
+ }
+
+ public EncryptionSupport encryptionSupportMode() {
return encryption;
}
}
diff --git
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
index 7df170cd361..71f33d9be57 100644
---
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
+++
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
@@ -3180,7 +3180,8 @@ public class LibvirtComputingResource extends
ServerResourceBase implements Serv
disk.setCacheMode(DiskDef.DiskCacheMode.valueOf(volumeObjectTO.getCacheMode().toString().toUpperCase()));
}
- if (volumeObjectTO.requiresEncryption()) {
+ if (volumeObjectTO.requiresEncryption() &&
+ pool.getType().encryptionSupportMode() ==
Storage.EncryptionSupport.Hypervisor ) {
String secretUuid = createLibvirtVolumeSecret(conn,
volumeObjectTO.getPath(), volumeObjectTO.getPassphrase());
DiskDef.LibvirtDiskEncryptDetails encryptDetails = new
DiskDef.LibvirtDiskEncryptDetails(secretUuid,
QemuObject.EncryptFormat.enumValue(volumeObjectTO.getEncryptFormat()));
disk.setLibvirtDiskEncryptDetails(encryptDetails);
diff --git
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
index 8cee8434b5e..d58cef8c79d 100644
---
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
+++
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java
@@ -50,6 +50,7 @@ import
com.cloud.hypervisor.kvm.resource.wrapper.LibvirtUtilitiesHelper;
import com.cloud.storage.JavaStorageLayer;
import com.cloud.storage.MigrationOptions;
import com.cloud.storage.ScopeType;
+import com.cloud.storage.Storage;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.StorageLayer;
@@ -1452,7 +1453,8 @@ public class KVMStorageProcessor implements
StorageProcessor {
}
}
- if (encryptDetails != null) {
+ if (encryptDetails != null &&
+ attachingPool.getType().encryptionSupportMode() ==
Storage.EncryptionSupport.Hypervisor) {
diskdef.setLibvirtDiskEncryptDetails(encryptDetails);
}
diff --git a/plugins/storage/volume/linstor/CHANGELOG.md
b/plugins/storage/volume/linstor/CHANGELOG.md
index 419a7f983ee..98fc8f69512 100644
--- a/plugins/storage/volume/linstor/CHANGELOG.md
+++ b/plugins/storage/volume/linstor/CHANGELOG.md
@@ -11,6 +11,11 @@ and this project adheres to [Semantic
Versioning](https://semver.org/spec/v2.0.0
- Volume snapshots on zfs used the wrong dataset path to hide/unhide snapdev
+## [2024-12-19]
+
+### Added
+- Native CloudStack encryption support
+
## [2024-12-13]
### Fixed
diff --git
a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorBackupSnapshotCommandWrapper.java
b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorBackupSnapshotCommandWrapper.java
index a572759c35a..fac2ccd2589 100644
---
a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorBackupSnapshotCommandWrapper.java
+++
b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LinstorBackupSnapshotCommandWrapper.java
@@ -96,18 +96,23 @@ public final class LinstorBackupSnapshotCommandWrapper
// NOTE: the qemu img will also contain the drbd metadata at the end
final QemuImg qemu = new QemuImg(waitMilliSeconds);
qemu.convert(srcFile, dstFile);
- s_logger.info("Backup snapshot " + srcFile + " to " + dstPath);
+ s_logger.info(String.format("Backup snapshot '%s' to '%s'", srcPath,
dstPath));
return dstPath;
}
private SnapshotObjectTO setCorrectSnapshotSize(final SnapshotObjectTO
dst, final String dstPath) {
final File snapFile = new File(dstPath);
- final long size = snapFile.exists() ? snapFile.length() : 0;
+ long size;
+ if (snapFile.exists()) {
+ size = snapFile.length();
+ } else {
+ s_logger.warn(String.format("Snapshot file %s does not exist.
Reporting size 0", dstPath));
+ size = 0;
+ }
- final SnapshotObjectTO snapshot = new SnapshotObjectTO();
- snapshot.setPath(dst.getPath() + File.separator + dst.getName());
- snapshot.setPhysicalSize(size);
- return snapshot;
+ dst.setPath(dst.getPath() + File.separator + dst.getName());
+ dst.setPhysicalSize(size);
+ return dst;
}
@Override
@@ -157,6 +162,7 @@ public final class LinstorBackupSnapshotCommandWrapper
s_logger.info("Backup shrunk " + dstPath + " to actual size " +
src.getVolume().getSize());
SnapshotObjectTO snapshot = setCorrectSnapshotSize(dst, dstPath);
+ s_logger.info(String.format("Actual file size for '%s' is %d",
dstPath, snapshot.getPhysicalSize()));
return new CopyCmdAnswer(snapshot);
} catch (final Exception e) {
final String error = String.format("Failed to backup snapshot with
id [%s] with a pool %s, due to %s",
diff --git
a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
index 9b7a376e8f2..6a4d6c7a349 100644
---
a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
+++
b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
@@ -407,7 +407,7 @@ public class LinstorStorageAdaptor implements
StorageAdaptor {
if (rsc.getFlags() != null &&
rsc.getFlags().contains(ApiConsts.FLAG_DRBD_DISKLESS)
&&
!rsc.getFlags().contains(ApiConsts.FLAG_TIE_BREAKER)) {
- ApiCallRcList delAnswers =
api.resourceDelete(rsc.getName(), localNodeName);
+ ApiCallRcList delAnswers =
api.resourceDelete(rsc.getName(), localNodeName, true);
logLinstorAnswers(delAnswers);
}
} catch (ApiException apiEx) {
diff --git
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
index 27904ed441b..4132fbd278a 100644
---
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
+++
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
@@ -21,11 +21,14 @@ import com.linbit.linstor.api.CloneWaiter;
import com.linbit.linstor.api.DevelopersApi;
import com.linbit.linstor.api.model.ApiCallRc;
import com.linbit.linstor.api.model.ApiCallRcList;
+import com.linbit.linstor.api.model.AutoSelectFilter;
+import com.linbit.linstor.api.model.LayerType;
import com.linbit.linstor.api.model.Properties;
import com.linbit.linstor.api.model.ResourceDefinition;
import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest;
import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted;
import com.linbit.linstor.api.model.ResourceDefinitionCreate;
+import com.linbit.linstor.api.model.ResourceGroup;
import com.linbit.linstor.api.model.ResourceGroupSpawn;
import com.linbit.linstor.api.model.ResourceMakeAvailable;
import com.linbit.linstor.api.model.Snapshot;
@@ -34,6 +37,7 @@ import com.linbit.linstor.api.model.VolumeDefinition;
import com.linbit.linstor.api.model.VolumeDefinitionModify;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import javax.inject.Inject;
import java.util.Arrays;
@@ -43,6 +47,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.stream.Collectors;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.storage.ResizeVolumeAnswer;
@@ -103,8 +108,11 @@ import
org.apache.cloudstack.storage.snapshot.SnapshotObject;
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.cloudstack.storage.volume.VolumeObject;
+import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
+import java.nio.charset.StandardCharsets;
+
public class LinstorPrimaryDataStoreDriverImpl implements
PrimaryDataStoreDriver {
private static final Logger s_logger =
Logger.getLogger(LinstorPrimaryDataStoreDriverImpl.class);
@Inject private PrimaryDataStoreDao _storagePoolDao;
@@ -393,11 +401,56 @@ public class LinstorPrimaryDataStoreDriverImpl implements
PrimaryDataStoreDriver
storagePoolVO.getUserInfo() : "DfltRscGrp";
}
+ /**
+ * Returns the layerlist of the resourceGroup with encryption(LUKS) added
above STORAGE.
+ * If the resourceGroup layer list already contains LUKS this layer list
will be returned.
+ * @param api Linstor developers API
+ * @param resourceGroup Resource group to get the encryption layer list
+ * @return layer list with LUKS added
+ */
+ public List<LayerType> getEncryptedLayerList(DevelopersApi api, String
resourceGroup) {
+ try {
+ List<ResourceGroup> rscGrps = api.resourceGroupList(
+ Collections.singletonList(resourceGroup),
Collections.emptyList(), null, null);
+
+ if (CollectionUtils.isEmpty(rscGrps)) {
+ throw new CloudRuntimeException(
+ String.format("Resource Group %s not found on Linstor
cluster.", resourceGroup));
+ }
+
+ final ResourceGroup rscGrp = rscGrps.get(0);
+ List<LayerType> layers = Arrays.asList(LayerType.DRBD,
LayerType.LUKS, LayerType.STORAGE);
+ List<String> curLayerStack = rscGrp.getSelectFilter() != null ?
+ rscGrp.getSelectFilter().getLayerStack() :
Collections.emptyList();
+ if (CollectionUtils.isNotEmpty(curLayerStack)) {
+ layers =
curLayerStack.stream().map(LayerType::valueOf).collect(Collectors.toList());
+ if (!layers.contains(LayerType.LUKS)) {
+ layers.add(layers.size() - 1, LayerType.LUKS); // lowest
layer is STORAGE
+ }
+ }
+ return layers;
+ } catch (ApiException e) {
+ throw new CloudRuntimeException(
+ String.format("Resource Group %s not found on Linstor
cluster.", resourceGroup));
+ }
+ }
+
private String createResourceBase(
- String rscName, long sizeInBytes, String volName, String vmName,
DevelopersApi api, String rscGrp) {
+ String rscName, long sizeInBytes, String volName, String vmName,
+ @Nullable Long passPhraseId, @Nullable byte[] passPhrase,
DevelopersApi api, String rscGrp) {
ResourceGroupSpawn rscGrpSpawn = new ResourceGroupSpawn();
rscGrpSpawn.setResourceDefinitionName(rscName);
rscGrpSpawn.addVolumeSizesItem(sizeInBytes / 1024);
+ if (passPhraseId != null) {
+ AutoSelectFilter asf = new AutoSelectFilter();
+ List<LayerType> luksLayers = getEncryptedLayerList(api, rscGrp);
+
asf.setLayerStack(luksLayers.stream().map(LayerType::toString).collect(Collectors.toList()));
+ rscGrpSpawn.setSelectFilter(asf);
+ if (passPhrase != null) {
+ String utf8Passphrase = new String(passPhrase,
StandardCharsets.UTF_8);
+
rscGrpSpawn.setVolumePassphrases(Collections.singletonList(utf8Passphrase));
+ }
+ }
try
{
@@ -422,7 +475,8 @@ public class LinstorPrimaryDataStoreDriverImpl implements
PrimaryDataStoreDriver
final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid();
String deviceName = createResourceBase(
- rscName, vol.getSize(), vol.getName(), vol.getAttachedVmName(),
linstorApi, rscGrp);
+ rscName, vol.getSize(), vol.getName(), vol.getAttachedVmName(),
vol.getPassphraseId(), vol.getPassphrase(),
+ linstorApi, rscGrp);
try
{
@@ -463,6 +517,14 @@ public class LinstorPrimaryDataStoreDriverImpl implements
PrimaryDataStoreDriver
s_logger.info("Clone resource definition " + cloneRes + " to "
+ rscName);
ResourceDefinitionCloneRequest cloneRequest = new
ResourceDefinitionCloneRequest();
cloneRequest.setName(rscName);
+ if (volumeInfo.getPassphraseId() != null) {
+ List<LayerType> encryptionLayer =
getEncryptedLayerList(linstorApi, getRscGrp(storagePoolVO));
+ cloneRequest.setLayerList(encryptionLayer);
+ if (volumeInfo.getPassphrase() != null) {
+ String utf8Passphrase = new
String(volumeInfo.getPassphrase(), StandardCharsets.UTF_8);
+
cloneRequest.setVolumePassphrases(Collections.singletonList(utf8Passphrase));
+ }
+ }
ResourceDefinitionCloneStarted cloneStarted =
linstorApi.resourceDefinitionClone(
cloneRes, cloneRequest);
@@ -915,6 +977,8 @@ public class LinstorPrimaryDataStoreDriverImpl implements
PrimaryDataStoreDriver
tInfo.getSize(),
tInfo.getName(),
"",
+ null,
+ null,
api,
getRscGrp(pool));
diff --git
a/plugins/storage/volume/linstor/src/test/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImplTest.java
b/plugins/storage/volume/linstor/src/test/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImplTest.java
new file mode 100644
index 00000000000..75276739468
--- /dev/null
+++
b/plugins/storage/volume/linstor/src/test/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImplTest.java
@@ -0,0 +1,87 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.storage.datastore.driver;
+
+import com.linbit.linstor.api.ApiException;
+import com.linbit.linstor.api.DevelopersApi;
+import com.linbit.linstor.api.model.AutoSelectFilter;
+import com.linbit.linstor.api.model.LayerType;
+import com.linbit.linstor.api.model.ResourceGroup;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class LinstorPrimaryDataStoreDriverImplTest {
+
+ private DevelopersApi api;
+
+ @InjectMocks
+ private LinstorPrimaryDataStoreDriverImpl linstorPrimaryDataStoreDriver;
+
+ @Before
+ public void setUp() {
+ api = mock(DevelopersApi.class);
+ }
+
+ @Test
+ public void testGetEncryptedLayerList() throws ApiException {
+ ResourceGroup dfltRscGrp = new ResourceGroup();
+ dfltRscGrp.setName("DfltRscGrp");
+
+ ResourceGroup bCacheRscGrp = new ResourceGroup();
+ bCacheRscGrp.setName("BcacheGrp");
+ AutoSelectFilter asf = new AutoSelectFilter();
+ asf.setLayerStack(Arrays.asList(LayerType.DRBD.name(),
LayerType.BCACHE.name(), LayerType.STORAGE.name()));
+ asf.setStoragePool("nvmePool");
+ bCacheRscGrp.setSelectFilter(asf);
+
+ ResourceGroup encryptedGrp = new ResourceGroup();
+ encryptedGrp.setName("EncryptedGrp");
+ AutoSelectFilter asf2 = new AutoSelectFilter();
+ asf2.setLayerStack(Arrays.asList(LayerType.DRBD.name(),
LayerType.LUKS.name(), LayerType.STORAGE.name()));
+ asf2.setStoragePool("ssdPool");
+ encryptedGrp.setSelectFilter(asf2);
+
+ when(api.resourceGroupList(Collections.singletonList("DfltRscGrp"),
Collections.emptyList(), null, null))
+ .thenReturn(Collections.singletonList(dfltRscGrp));
+ when(api.resourceGroupList(Collections.singletonList("BcacheGrp"),
Collections.emptyList(), null, null))
+ .thenReturn(Collections.singletonList(bCacheRscGrp));
+ when(api.resourceGroupList(Collections.singletonList("EncryptedGrp"),
Collections.emptyList(), null, null))
+ .thenReturn(Collections.singletonList(encryptedGrp));
+
+ List<LayerType> layers =
linstorPrimaryDataStoreDriver.getEncryptedLayerList(api, "DfltRscGrp");
+ Assert.assertEquals(Arrays.asList(LayerType.DRBD, LayerType.LUKS,
LayerType.STORAGE), layers);
+
+ layers = linstorPrimaryDataStoreDriver.getEncryptedLayerList(api,
"BcacheGrp");
+ Assert.assertEquals(Arrays.asList(LayerType.DRBD, LayerType.BCACHE,
LayerType.LUKS, LayerType.STORAGE), layers);
+
+ layers = linstorPrimaryDataStoreDriver.getEncryptedLayerList(api,
"EncryptedGrp");
+ Assert.assertEquals(Arrays.asList(LayerType.DRBD, LayerType.LUKS,
LayerType.STORAGE), layers);
+ }
+}
diff --git
a/plugins/storage/volume/linstor/src/test/java/org/apache/cloudstack/storage/datastore/util/LinstorUtilTest.java
b/plugins/storage/volume/linstor/src/test/java/org/apache/cloudstack/storage/datastore/util/LinstorUtilTest.java
new file mode 100644
index 00000000000..55f0c6ebe6d
--- /dev/null
+++
b/plugins/storage/volume/linstor/src/test/java/org/apache/cloudstack/storage/datastore/util/LinstorUtilTest.java
@@ -0,0 +1,127 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.storage.datastore.util;
+
+import com.linbit.linstor.api.ApiException;
+import com.linbit.linstor.api.DevelopersApi;
+import com.linbit.linstor.api.model.AutoSelectFilter;
+import com.linbit.linstor.api.model.Node;
+import com.linbit.linstor.api.model.Properties;
+import com.linbit.linstor.api.model.ProviderKind;
+import com.linbit.linstor.api.model.ResourceGroup;
+import com.linbit.linstor.api.model.StoragePool;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class LinstorUtilTest {
+
+ private static final String LINSTOR_URL_TEST = "devnull.com:3370";
+ private DevelopersApi api;
+
+ private Node mockNode(String name) {
+ Node nodeMock = new Node();
+ nodeMock.setName(name);
+
+ return nodeMock;
+ }
+
+ private StoragePool mockStoragePool(String name, String node, ProviderKind
kind) {
+ StoragePool sp = new StoragePool();
+ sp.setStoragePoolName(name);
+ sp.setNodeName(node);
+ sp.setProviderKind(kind);
+ return sp;
+ }
+
+ @Before
+ public void setUp() throws ApiException {
+ api = mock(DevelopersApi.class);
+
+ when(api.nodeList(Collections.emptyList(), Collections.emptyList(),
null, null))
+ .thenReturn(Arrays.asList(mockNode("nodeA"),
mockNode("nodeB"), mockNode("nodeC")));
+
+ ResourceGroup csGroup = new ResourceGroup();
+ csGroup.setName("cloudstack");
+ AutoSelectFilter asf = new AutoSelectFilter();
+ asf.setPlaceCount(2);
+ csGroup.setSelectFilter(asf);
+ when(api.resourceGroupList(Collections.singletonList("cloudstack"),
null, null, null))
+ .thenReturn(Collections.singletonList(csGroup));
+
+ when(api.viewStoragePools(Collections.emptyList(), null, null, null,
null, true))
+ .thenReturn(Arrays.asList(
+ mockStoragePool("thinpool", "nodeA",
ProviderKind.LVM_THIN),
+ mockStoragePool("thinpool", "nodeB",
ProviderKind.LVM_THIN),
+ mockStoragePool("thinpool", "nodeC",
ProviderKind.LVM_THIN)
+ ));
+
+// when(LinstorUtil.getLinstorAPI(LINSTOR_URL_TEST)).thenReturn(api);
+ }
+
+ @Test
+ public void testGetLinstorNodeNames() throws ApiException {
+ List<String> linstorNodes = LinstorUtil.getLinstorNodeNames(api);
+ Assert.assertEquals(Arrays.asList("nodeA", "nodeB", "nodeC"),
linstorNodes);
+ }
+
+ @Test
+ public void testGetSnapshotPath() {
+ {
+ StoragePool spLVMThin = new StoragePool();
+ Properties lvmThinProps = new Properties();
+ lvmThinProps.put("StorDriver/StorPoolName",
"storage/storage-thin");
+ spLVMThin.setProps(lvmThinProps);
+ spLVMThin.setProviderKind(ProviderKind.LVM_THIN);
+ String snapPath = LinstorUtil.getSnapshotPath(spLVMThin,
"cs-cb32532a-dd8f-47e0-a81c-8a75573d3545", "snap3");
+
Assert.assertEquals("/dev/mapper/storage-cs--cb32532a--dd8f--47e0--a81c--8a75573d3545_00000_snap3",
snapPath);
+ }
+
+ {
+ StoragePool spZFS = new StoragePool();
+ Properties zfsProps = new Properties();
+ zfsProps.put("StorDriver/StorPoolName", "linstorPool");
+ spZFS.setProps(zfsProps);
+ spZFS.setProviderKind(ProviderKind.ZFS);
+
+ String snapPath = LinstorUtil.getSnapshotPath(spZFS,
"cs-cb32532a-dd8f-47e0-a81c-8a75573d3545", "snap2");
+
Assert.assertEquals("zfs://linstorPool/cs-cb32532a-dd8f-47e0-a81c-8a75573d3545_00000@snap2",
snapPath);
+ }
+ }
+
+ @Test
+ public void testGetRscGroupStoragePools() throws ApiException {
+ List<StoragePool> storagePools =
LinstorUtil.getRscGroupStoragePools(api, "cloudstack");
+
+ List<String> names = storagePools.stream()
+ .map(sp -> String.format("%s::%s", sp.getNodeName(),
sp.getStoragePoolName()))
+ .collect(Collectors.toList());
+ Assert.assertEquals(names, Arrays.asList("nodeA::thinpool",
"nodeB::thinpool", "nodeC::thinpool"));
+ }
+}
diff --git a/pom.xml b/pom.xml
index da0252acbb4..0771a124db2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -169,7 +169,7 @@
<cs.nitro.version>10.1</cs.nitro.version>
<cs.opensaml.version>2.6.6</cs.opensaml.version>
<cs.rados-java.version>0.6.0</cs.rados-java.version>
- <cs.java-linstor.version>0.5.2</cs.java-linstor.version>
+ <cs.java-linstor.version>0.6.0</cs.java-linstor.version>
<cs.reflections.version>0.10.2</cs.reflections.version>
<cs.servicemix.version>3.4.4_1</cs.servicemix.version>
<cs.servlet.version>4.0.1</cs.servlet.version>