mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
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:
parent
1b2f6c9998
commit
55e8eaab89
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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));
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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"));
|
||||
}
|
||||
}
|
||||
2
pom.xml
2
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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user