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>

Reply via email to