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-) + 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-) 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 getEncryptedLayerList(DevelopersApi api, String resourceGroup) { + try { + List 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 layers = Arrays.asList(LayerType.DRBD, LayerType.LUKS, LayerType.STORAGE); + List 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 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 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 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 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 storagePools = LinstorUtil.getRscGroupStoragePools(api, "cloudstack"); + + List 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 @@ 10.1 2.6.6 0.6.0 - 0.5.2 + 0.6.0 0.10.2 3.4.4_1 4.0.1