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.
This commit is contained in:
Rene Peinthor 2025-02-04 15:18:49 +01:00 committed by GitHub
parent 1b2f6c9998
commit 55e8eaab89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 346 additions and 35 deletions

View File

@ -135,34 +135,49 @@ public class Storage {
ISODISK /* Template corresponding to a iso (non root disk) present in an OVA */ 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 { public static enum StoragePoolType {
Filesystem(false, true, true), // local directory Filesystem(false, true, EncryptionSupport.Hypervisor), // local directory
NetworkFilesystem(true, true, true), // NFS NetworkFilesystem(true, true, EncryptionSupport.Hypervisor), // NFS
IscsiLUN(true, false, false), // shared LUN, with a clusterfs overlay IscsiLUN(true, false, EncryptionSupport.Unsupported), // shared LUN, with a clusterfs overlay
Iscsi(true, false, false), // for e.g., ZFS Comstar Iscsi(true, false, EncryptionSupport.Unsupported), // for e.g., ZFS Comstar
ISO(false, false, false), // for iso image ISO(false, false, EncryptionSupport.Unsupported), // for iso image
LVM(false, false, false), // XenServer local LVM SR LVM(false, false, EncryptionSupport.Unsupported), // XenServer local LVM SR
CLVM(true, false, false), CLVM(true, false, EncryptionSupport.Unsupported),
RBD(true, true, false), // http://libvirt.org/storage.html#StorageBackendRBD RBD(true, true, EncryptionSupport.Unsupported), // http://libvirt.org/storage.html#StorageBackendRBD
SharedMountPoint(true, true, true), SharedMountPoint(true, true, EncryptionSupport.Hypervisor),
VMFS(true, true, false), // VMware VMFS storage VMFS(true, true, EncryptionSupport.Unsupported), // VMware VMFS storage
PreSetup(true, true, false), // for XenServer, Storage Pool is set up by customers. PreSetup(true, true, EncryptionSupport.Unsupported), // for XenServer, Storage Pool is set up by customers.
EXT(false, true, false), // XenServer local EXT SR EXT(false, true, EncryptionSupport.Unsupported), // XenServer local EXT SR
OCFS2(true, false, false), OCFS2(true, false, EncryptionSupport.Unsupported),
SMB(true, false, false), SMB(true, false, EncryptionSupport.Unsupported),
Gluster(true, false, false), Gluster(true, false, EncryptionSupport.Unsupported),
PowerFlex(true, true, true), // Dell EMC PowerFlex/ScaleIO (formerly VxFlexOS) PowerFlex(true, true, EncryptionSupport.Hypervisor), // Dell EMC PowerFlex/ScaleIO (formerly VxFlexOS)
ManagedNFS(true, false, false), ManagedNFS(true, false, EncryptionSupport.Unsupported),
Linstor(true, true, false), Linstor(true, true, EncryptionSupport.Storage),
DatastoreCluster(true, true, false), // for VMware, to abstract pool of clusters DatastoreCluster(true, true, EncryptionSupport.Unsupported), // for VMware, to abstract pool of clusters
StorPool(true, true, true), StorPool(true, true, EncryptionSupport.Hypervisor),
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>) 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 shared;
private final boolean overProvisioning; 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.shared = shared;
this.overProvisioning = overProvisioning; this.overProvisioning = overProvisioning;
this.encryption = encryption; this.encryption = encryption;
@ -177,6 +192,10 @@ public class Storage {
} }
public boolean supportsEncryption() { public boolean supportsEncryption() {
return encryption == EncryptionSupport.Hypervisor || encryption == EncryptionSupport.Storage;
}
public EncryptionSupport encryptionSupportMode() {
return encryption; return encryption;
} }
} }

View File

@ -3180,7 +3180,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
disk.setCacheMode(DiskDef.DiskCacheMode.valueOf(volumeObjectTO.getCacheMode().toString().toUpperCase())); 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()); String secretUuid = createLibvirtVolumeSecret(conn, volumeObjectTO.getPath(), volumeObjectTO.getPassphrase());
DiskDef.LibvirtDiskEncryptDetails encryptDetails = new DiskDef.LibvirtDiskEncryptDetails(secretUuid, QemuObject.EncryptFormat.enumValue(volumeObjectTO.getEncryptFormat())); DiskDef.LibvirtDiskEncryptDetails encryptDetails = new DiskDef.LibvirtDiskEncryptDetails(secretUuid, QemuObject.EncryptFormat.enumValue(volumeObjectTO.getEncryptFormat()));
disk.setLibvirtDiskEncryptDetails(encryptDetails); disk.setLibvirtDiskEncryptDetails(encryptDetails);

View File

@ -50,6 +50,7 @@ import com.cloud.hypervisor.kvm.resource.wrapper.LibvirtUtilitiesHelper;
import com.cloud.storage.JavaStorageLayer; import com.cloud.storage.JavaStorageLayer;
import com.cloud.storage.MigrationOptions; import com.cloud.storage.MigrationOptions;
import com.cloud.storage.ScopeType; import com.cloud.storage.ScopeType;
import com.cloud.storage.Storage;
import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.StorageLayer; 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); diskdef.setLibvirtDiskEncryptDetails(encryptDetails);
} }

View File

@ -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 - Volume snapshots on zfs used the wrong dataset path to hide/unhide snapdev
## [2024-12-19]
### Added
- Native CloudStack encryption support
## [2024-12-13] ## [2024-12-13]
### Fixed ### Fixed

View File

@ -96,18 +96,23 @@ public final class LinstorBackupSnapshotCommandWrapper
// NOTE: the qemu img will also contain the drbd metadata at the end // NOTE: the qemu img will also contain the drbd metadata at the end
final QemuImg qemu = new QemuImg(waitMilliSeconds); final QemuImg qemu = new QemuImg(waitMilliSeconds);
qemu.convert(srcFile, dstFile); 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; return dstPath;
} }
private SnapshotObjectTO setCorrectSnapshotSize(final SnapshotObjectTO dst, final String dstPath) { private SnapshotObjectTO setCorrectSnapshotSize(final SnapshotObjectTO dst, final String dstPath) {
final File snapFile = new File(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(); dst.setPath(dst.getPath() + File.separator + dst.getName());
snapshot.setPath(dst.getPath() + File.separator + dst.getName()); dst.setPhysicalSize(size);
snapshot.setPhysicalSize(size); return dst;
return snapshot;
} }
@Override @Override
@ -157,6 +162,7 @@ public final class LinstorBackupSnapshotCommandWrapper
s_logger.info("Backup shrunk " + dstPath + " to actual size " + src.getVolume().getSize()); s_logger.info("Backup shrunk " + dstPath + " to actual size " + src.getVolume().getSize());
SnapshotObjectTO snapshot = setCorrectSnapshotSize(dst, dstPath); SnapshotObjectTO snapshot = setCorrectSnapshotSize(dst, dstPath);
s_logger.info(String.format("Actual file size for '%s' is %d", dstPath, snapshot.getPhysicalSize()));
return new CopyCmdAnswer(snapshot); return new CopyCmdAnswer(snapshot);
} catch (final Exception e) { } catch (final Exception e) {
final String error = String.format("Failed to backup snapshot with id [%s] with a pool %s, due to %s", final String error = String.format("Failed to backup snapshot with id [%s] with a pool %s, due to %s",

View File

@ -407,7 +407,7 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
if (rsc.getFlags() != null && if (rsc.getFlags() != null &&
rsc.getFlags().contains(ApiConsts.FLAG_DRBD_DISKLESS) && rsc.getFlags().contains(ApiConsts.FLAG_DRBD_DISKLESS) &&
!rsc.getFlags().contains(ApiConsts.FLAG_TIE_BREAKER)) { !rsc.getFlags().contains(ApiConsts.FLAG_TIE_BREAKER)) {
ApiCallRcList delAnswers = api.resourceDelete(rsc.getName(), localNodeName); ApiCallRcList delAnswers = api.resourceDelete(rsc.getName(), localNodeName, true);
logLinstorAnswers(delAnswers); logLinstorAnswers(delAnswers);
} }
} catch (ApiException apiEx) { } catch (ApiException apiEx) {

View File

@ -21,11 +21,14 @@ import com.linbit.linstor.api.CloneWaiter;
import com.linbit.linstor.api.DevelopersApi; import com.linbit.linstor.api.DevelopersApi;
import com.linbit.linstor.api.model.ApiCallRc; import com.linbit.linstor.api.model.ApiCallRc;
import com.linbit.linstor.api.model.ApiCallRcList; 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.Properties;
import com.linbit.linstor.api.model.ResourceDefinition; import com.linbit.linstor.api.model.ResourceDefinition;
import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest; import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest;
import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted; import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted;
import com.linbit.linstor.api.model.ResourceDefinitionCreate; 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.ResourceGroupSpawn;
import com.linbit.linstor.api.model.ResourceMakeAvailable; import com.linbit.linstor.api.model.ResourceMakeAvailable;
import com.linbit.linstor.api.model.Snapshot; 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 com.linbit.linstor.api.model.VolumeDefinitionModify;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.Arrays; import java.util.Arrays;
@ -43,6 +47,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import com.cloud.agent.api.Answer; import com.cloud.agent.api.Answer;
import com.cloud.agent.api.storage.ResizeVolumeAnswer; 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.SnapshotObjectTO;
import org.apache.cloudstack.storage.to.VolumeObjectTO; import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.cloudstack.storage.volume.VolumeObject; import org.apache.cloudstack.storage.volume.VolumeObject;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import java.nio.charset.StandardCharsets;
public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver { public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver {
private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreDriverImpl.class); private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreDriverImpl.class);
@Inject private PrimaryDataStoreDao _storagePoolDao; @Inject private PrimaryDataStoreDao _storagePoolDao;
@ -393,11 +401,56 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
storagePoolVO.getUserInfo() : "DfltRscGrp"; 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( 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(); ResourceGroupSpawn rscGrpSpawn = new ResourceGroupSpawn();
rscGrpSpawn.setResourceDefinitionName(rscName); rscGrpSpawn.setResourceDefinitionName(rscName);
rscGrpSpawn.addVolumeSizesItem(sizeInBytes / 1024); 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 try
{ {
@ -422,7 +475,8 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid(); final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid();
String deviceName = createResourceBase( 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 try
{ {
@ -463,6 +517,14 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
s_logger.info("Clone resource definition " + cloneRes + " to " + rscName); s_logger.info("Clone resource definition " + cloneRes + " to " + rscName);
ResourceDefinitionCloneRequest cloneRequest = new ResourceDefinitionCloneRequest(); ResourceDefinitionCloneRequest cloneRequest = new ResourceDefinitionCloneRequest();
cloneRequest.setName(rscName); 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( ResourceDefinitionCloneStarted cloneStarted = linstorApi.resourceDefinitionClone(
cloneRes, cloneRequest); cloneRes, cloneRequest);
@ -915,6 +977,8 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
tInfo.getSize(), tInfo.getSize(),
tInfo.getName(), tInfo.getName(),
"", "",
null,
null,
api, api,
getRscGrp(pool)); getRscGrp(pool));

View File

@ -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);
}
}

View File

@ -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"));
}
}

View File

@ -169,7 +169,7 @@
<cs.nitro.version>10.1</cs.nitro.version> <cs.nitro.version>10.1</cs.nitro.version>
<cs.opensaml.version>2.6.6</cs.opensaml.version> <cs.opensaml.version>2.6.6</cs.opensaml.version>
<cs.rados-java.version>0.6.0</cs.rados-java.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.reflections.version>0.10.2</cs.reflections.version>
<cs.servicemix.version>3.4.4_1</cs.servicemix.version> <cs.servicemix.version>3.4.4_1</cs.servicemix.version>
<cs.servlet.version>4.0.1</cs.servlet.version> <cs.servlet.version>4.0.1</cs.servlet.version>