mirror of https://github.com/apache/cloudstack.git
Add support to RBD erasure code pools (#9808)
* Readd filename string on qemuimg create * Remove empty object on the data pool details of storage pools with no data pool * Only use the method createPhysicalDiskByLibVirt with RBD when the pool is of erasure code type. Also added javadoc for createPhysicalDisk method * Change literal '/' string to File.separator * Add support for erasure code pools * Fix null on putAll
This commit is contained in:
parent
2dfe6a6333
commit
cb4848bc1a
|
|
@ -149,6 +149,10 @@ public class StoragePoolResponse extends BaseResponseWithAnnotations {
|
|||
@Param(description = "whether this pool is managed or not")
|
||||
private Boolean managed;
|
||||
|
||||
@SerializedName(ApiConstants.DETAILS)
|
||||
@Param(description = "the storage pool details")
|
||||
private Map<String, String> details;
|
||||
|
||||
public Map<String, String> getCaps() {
|
||||
return caps;
|
||||
}
|
||||
|
|
@ -407,4 +411,12 @@ public class StoragePoolResponse extends BaseResponseWithAnnotations {
|
|||
public void setManaged(Boolean managed) {
|
||||
this.managed = managed;
|
||||
}
|
||||
|
||||
public Map<String, String> getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
public void setDetails(Map<String, String> details) {
|
||||
this.details = details;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ import com.cloud.storage.StoragePool;
|
|||
import com.cloud.storage.StoragePoolHostVO;
|
||||
import com.cloud.storage.StorageService;
|
||||
import com.cloud.storage.dao.StoragePoolHostDao;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
|
||||
|
|
@ -60,6 +59,7 @@ import javax.inject.Inject;
|
|||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class DefaultHostListener implements HypervisorHostListener {
|
||||
protected Logger logger = LogManager.getLogger(getClass());
|
||||
|
|
@ -133,9 +133,11 @@ public class DefaultHostListener implements HypervisorHostListener {
|
|||
@Override
|
||||
public boolean hostConnect(long hostId, long poolId) throws StorageConflictException {
|
||||
StoragePool pool = (StoragePool) this.dataStoreMgr.getDataStore(poolId, DataStoreRole.Primary);
|
||||
Pair<Map<String, String>, Boolean> nfsMountOpts = storageManager.getStoragePoolNFSMountOpts(pool, null);
|
||||
Map<String, String> detailsMap = storagePoolDetailsDao.listDetailsKeyPairs(poolId);
|
||||
Map<String, String> nfsMountOpts = storageManager.getStoragePoolNFSMountOpts(pool, null).first();
|
||||
|
||||
ModifyStoragePoolCommand cmd = new ModifyStoragePoolCommand(true, pool, nfsMountOpts.first());
|
||||
Optional.ofNullable(nfsMountOpts).ifPresent(detailsMap::putAll);
|
||||
ModifyStoragePoolCommand cmd = new ModifyStoragePoolCommand(true, pool, detailsMap);
|
||||
cmd.setWait(modifyStoragePoolCommandWait);
|
||||
HostVO host = hostDao.findById(hostId);
|
||||
logger.debug("Sending modify storage pool command to agent: {} for storage pool: {} with timeout {} seconds", host, pool, cmd.getWait());
|
||||
|
|
|
|||
|
|
@ -108,9 +108,7 @@ public final class LibvirtCreatePrivateTemplateFromVolumeCommandWrapper extends
|
|||
} else {
|
||||
logger.debug("Converting RBD disk " + disk.getPath() + " into template " + command.getUniqueName());
|
||||
|
||||
final QemuImgFile srcFile =
|
||||
new QemuImgFile(KVMPhysicalDisk.RBDStringBuilder(primary.getSourceHost(), primary.getSourcePort(), primary.getAuthUserName(),
|
||||
primary.getAuthSecret(), disk.getPath()));
|
||||
final QemuImgFile srcFile = new QemuImgFile(KVMPhysicalDisk.RBDStringBuilder(primary, disk.getPath()));
|
||||
srcFile.setFormat(PhysicalDiskFormat.RAW);
|
||||
|
||||
final QemuImgFile destFile = new QemuImgFile(tmpltPath + "/" + command.getUniqueName() + ".qcow2");
|
||||
|
|
|
|||
|
|
@ -161,11 +161,7 @@ public final class LibvirtGetVolumesOnStorageCommandWrapper extends CommandWrapp
|
|||
QemuImg qemu = new QemuImg(0);
|
||||
QemuImgFile qemuFile = new QemuImgFile(disk.getPath(), disk.getFormat());
|
||||
if (StoragePoolType.RBD.equals(pool.getType())) {
|
||||
String rbdDestFile = KVMPhysicalDisk.RBDStringBuilder(pool.getSourceHost(),
|
||||
pool.getSourcePort(),
|
||||
pool.getAuthUserName(),
|
||||
pool.getAuthSecret(),
|
||||
disk.getPath());
|
||||
String rbdDestFile = KVMPhysicalDisk.RBDStringBuilder(pool, disk.getPath());
|
||||
qemuFile = new QemuImgFile(rbdDestFile, disk.getFormat());
|
||||
}
|
||||
return qemu.info(qemuFile, secure);
|
||||
|
|
|
|||
|
|
@ -410,9 +410,7 @@ public class IscsiAdmStorageAdaptor implements StorageAdaptor {
|
|||
KVMStoragePool srcPool = srcDisk.getPool();
|
||||
|
||||
if (srcPool.getType() == StoragePoolType.RBD) {
|
||||
srcFile = new QemuImgFile(KVMPhysicalDisk.RBDStringBuilder(srcPool.getSourceHost(), srcPool.getSourcePort(),
|
||||
srcPool.getAuthUserName(), srcPool.getAuthSecret(),
|
||||
srcDisk.getPath()),srcDisk.getFormat());
|
||||
srcFile = new QemuImgFile(KVMPhysicalDisk.RBDStringBuilder(srcPool, srcDisk.getPath()), srcDisk.getFormat());
|
||||
} else {
|
||||
srcFile = new QemuImgFile(srcDisk.getPath(), srcDisk.getFormat());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import org.apache.commons.lang3.StringUtils;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class KVMPhysicalDisk {
|
||||
private String path;
|
||||
|
|
@ -32,10 +33,17 @@ public class KVMPhysicalDisk {
|
|||
private String vmName;
|
||||
private boolean useAsTemplate;
|
||||
|
||||
public static String RBDStringBuilder(String monHost, int monPort, String authUserName, String authSecret, String image) {
|
||||
String rbdOpts;
|
||||
public static final String RBD_DEFAULT_DATA_POOL = "rbd_default_data_pool";
|
||||
|
||||
rbdOpts = "rbd:" + image;
|
||||
public static String RBDStringBuilder(KVMStoragePool storagePool, String image) {
|
||||
String monHost = storagePool.getSourceHost();
|
||||
int monPort = storagePool.getSourcePort();
|
||||
String authUserName = storagePool.getAuthUserName();
|
||||
String authSecret = storagePool.getAuthSecret();
|
||||
Map<String, String> details = storagePool.getDetails();
|
||||
String dataPool = (details == null) ? null : details.get(RBD_DEFAULT_DATA_POOL);
|
||||
|
||||
String rbdOpts = "rbd:" + image;
|
||||
rbdOpts += ":mon_host=" + composeOptionForMonHosts(monHost, monPort);
|
||||
|
||||
if (authUserName == null) {
|
||||
|
|
@ -46,6 +54,10 @@ public class KVMPhysicalDisk {
|
|||
rbdOpts += ":key=" + authSecret;
|
||||
}
|
||||
|
||||
if (dataPool != null) {
|
||||
rbdOpts += String.format(":rbd_default_data_pool=%s", dataPool);
|
||||
}
|
||||
|
||||
rbdOpts += ":rbd_default_format=2";
|
||||
rbdOpts += ":client_mount_timeout=30";
|
||||
|
||||
|
|
|
|||
|
|
@ -53,28 +53,6 @@ import com.cloud.vm.VirtualMachine;
|
|||
public class KVMStoragePoolManager {
|
||||
protected Logger logger = LogManager.getLogger(getClass());
|
||||
|
||||
private class StoragePoolInformation {
|
||||
String name;
|
||||
String host;
|
||||
int port;
|
||||
String path;
|
||||
String userInfo;
|
||||
boolean type;
|
||||
StoragePoolType poolType;
|
||||
Map<String, String> details;
|
||||
|
||||
public StoragePoolInformation(String name, String host, int port, String path, String userInfo, StoragePoolType poolType, Map<String, String> details, boolean type) {
|
||||
this.name = name;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.path = path;
|
||||
this.userInfo = userInfo;
|
||||
this.type = type;
|
||||
this.poolType = poolType;
|
||||
this.details = details;
|
||||
}
|
||||
}
|
||||
|
||||
private KVMHAMonitor _haMonitor;
|
||||
private final Map<String, StoragePoolInformation> _storagePools = new ConcurrentHashMap<String, StoragePoolInformation>();
|
||||
private final Map<String, StorageAdaptor> _storageMapper = new HashMap<String, StorageAdaptor>();
|
||||
|
|
@ -303,14 +281,33 @@ public class KVMStoragePoolManager {
|
|||
} catch (Exception e) {
|
||||
StoragePoolInformation info = _storagePools.get(uuid);
|
||||
if (info != null) {
|
||||
pool = createStoragePool(info.name, info.host, info.port, info.path, info.userInfo, info.poolType, info.details, info.type);
|
||||
pool = createStoragePool(info.getName(), info.getHost(), info.getPort(), info.getPath(), info.getUserInfo(), info.getPoolType(), info.getDetails(), info.isType());
|
||||
} else {
|
||||
throw new CloudRuntimeException("Could not fetch storage pool " + uuid + " from libvirt due to " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (pool instanceof LibvirtStoragePool) {
|
||||
addPoolDetails(uuid, (LibvirtStoragePool) pool);
|
||||
}
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* As the class {@link LibvirtStoragePool} is constrained to the {@link org.libvirt.StoragePool} class, there is no way of saving a generic parameter such as the details, hence,
|
||||
* this method was created to always make available the details of libvirt primary storages for when they are needed.
|
||||
*/
|
||||
private void addPoolDetails(String uuid, LibvirtStoragePool pool) {
|
||||
StoragePoolInformation storagePoolInformation = _storagePools.get(uuid);
|
||||
Map<String, String> details = storagePoolInformation.getDetails();
|
||||
|
||||
if (MapUtils.isNotEmpty(details)) {
|
||||
logger.trace("Adding the details {} to the pool with UUID {}.", details, uuid);
|
||||
pool.setDetails(details);
|
||||
}
|
||||
}
|
||||
|
||||
public KVMStoragePool getStoragePoolByURI(String uri) {
|
||||
URI storageUri = null;
|
||||
|
||||
|
|
|
|||
|
|
@ -667,9 +667,7 @@ public class KVMStorageProcessor implements StorageProcessor {
|
|||
} else {
|
||||
logger.debug("Converting RBD disk " + disk.getPath() + " into template " + templateName);
|
||||
|
||||
final QemuImgFile srcFile =
|
||||
new QemuImgFile(KVMPhysicalDisk.RBDStringBuilder(primary.getSourceHost(), primary.getSourcePort(), primary.getAuthUserName(),
|
||||
primary.getAuthSecret(), disk.getPath()));
|
||||
final QemuImgFile srcFile = new QemuImgFile(KVMPhysicalDisk.RBDStringBuilder(primary, disk.getPath()));
|
||||
srcFile.setFormat(PhysicalDiskFormat.RAW);
|
||||
|
||||
final QemuImgFile destFile = new QemuImgFile(tmpltPath + "/" + templateName + ".qcow2");
|
||||
|
|
@ -1022,9 +1020,7 @@ public class KVMStorageProcessor implements StorageProcessor {
|
|||
logger.debug("Attempting to create " + snapDir.getAbsolutePath() + " recursively for snapshot storage");
|
||||
FileUtils.forceMkdir(snapDir);
|
||||
|
||||
final QemuImgFile srcFile =
|
||||
new QemuImgFile(KVMPhysicalDisk.RBDStringBuilder(primaryPool.getSourceHost(), primaryPool.getSourcePort(), primaryPool.getAuthUserName(),
|
||||
primaryPool.getAuthSecret(), rbdSnapshot));
|
||||
final QemuImgFile srcFile = new QemuImgFile(KVMPhysicalDisk.RBDStringBuilder(primaryPool, rbdSnapshot));
|
||||
srcFile.setFormat(snapshotDisk.getFormat());
|
||||
|
||||
final QemuImgFile destFile = new QemuImgFile(snapshotFile);
|
||||
|
|
|
|||
|
|
@ -960,17 +960,55 @@ public class LibvirtStorageAdaptor implements StorageAdaptor {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a physical disk depending on the {@link StoragePoolType}:
|
||||
* <ul>
|
||||
* <li>
|
||||
* <b>{@link StoragePoolType#RBD}</b>
|
||||
* <ul>
|
||||
* <li>
|
||||
* If it is an erasure code pool, utilizes QemuImg to create the physical disk through the method
|
||||
* {@link LibvirtStorageAdaptor#createPhysicalDiskByQemuImg(String, KVMStoragePool, PhysicalDiskFormat, Storage.ProvisioningType, long, byte[])}
|
||||
* </li>
|
||||
* <li>
|
||||
* Otherwise, utilize Libvirt to create the physical disk through the method
|
||||
* {@link LibvirtStorageAdaptor#createPhysicalDiskByLibVirt(String, KVMStoragePool, PhysicalDiskFormat, Storage.ProvisioningType, long)}
|
||||
* </li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li>
|
||||
* {@link StoragePoolType#NetworkFilesystem} and {@link StoragePoolType#Filesystem}
|
||||
* <ul>
|
||||
* <li>
|
||||
* If the format is {@link PhysicalDiskFormat#QCOW2} or {@link PhysicalDiskFormat#RAW}, utilizes QemuImg to create the physical disk through the method
|
||||
* {@link LibvirtStorageAdaptor#createPhysicalDiskByQemuImg(String, KVMStoragePool, PhysicalDiskFormat, Storage.ProvisioningType, long, byte[])}
|
||||
* </li>
|
||||
* <li>
|
||||
* If the format is {@link PhysicalDiskFormat#DIR} or {@link PhysicalDiskFormat#TAR}, utilize Libvirt to create the physical disk through the method
|
||||
* {@link LibvirtStorageAdaptor#createPhysicalDiskByLibVirt(String, KVMStoragePool, PhysicalDiskFormat, Storage.ProvisioningType, long)}
|
||||
* </li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li>
|
||||
* For the rest of the {@link StoragePoolType} types, utilizes the Libvirt method
|
||||
* {@link LibvirtStorageAdaptor#createPhysicalDiskByLibVirt(String, KVMStoragePool, PhysicalDiskFormat, Storage.ProvisioningType, long)}
|
||||
* </li>
|
||||
* </ul>
|
||||
*/
|
||||
@Override
|
||||
public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool,
|
||||
PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, byte[] passphrase) {
|
||||
|
||||
logger.info("Attempting to create volume " + name + " (" + pool.getType().toString() + ") in pool "
|
||||
+ pool.getUuid() + " with size " + toHumanReadableSize(size));
|
||||
logger.info("Attempting to create volume {} ({}) in pool {} with size {}", name, pool.getType().toString(), pool.getUuid(), toHumanReadableSize(size));
|
||||
|
||||
StoragePoolType poolType = pool.getType();
|
||||
if (poolType.equals(StoragePoolType.RBD)) {
|
||||
return createPhysicalDiskByLibVirt(name, pool, PhysicalDiskFormat.RAW, provisioningType, size);
|
||||
} else if (poolType.equals(StoragePoolType.NetworkFilesystem) || poolType.equals(StoragePoolType.Filesystem)) {
|
||||
if (StoragePoolType.RBD.equals(poolType)) {
|
||||
Map<String, String> details = pool.getDetails();
|
||||
String dataPool = (details == null) ? null : details.get(KVMPhysicalDisk.RBD_DEFAULT_DATA_POOL);
|
||||
|
||||
return (dataPool == null) ? createPhysicalDiskByLibVirt(name, pool, PhysicalDiskFormat.RAW, provisioningType, size) :
|
||||
createPhysicalDiskByQemuImg(name, pool, PhysicalDiskFormat.RAW, provisioningType, size, passphrase);
|
||||
} else if (StoragePoolType.NetworkFilesystem.equals(poolType) || StoragePoolType.Filesystem.equals(poolType)) {
|
||||
switch (format) {
|
||||
case QCOW2:
|
||||
case RAW:
|
||||
|
|
@ -1018,18 +1056,25 @@ public class LibvirtStorageAdaptor implements StorageAdaptor {
|
|||
}
|
||||
|
||||
|
||||
private KVMPhysicalDisk createPhysicalDiskByQemuImg(String name, KVMStoragePool pool,
|
||||
PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, byte[] passphrase) {
|
||||
String volPath = pool.getLocalPath() + "/" + name;
|
||||
private KVMPhysicalDisk createPhysicalDiskByQemuImg(String name, KVMStoragePool pool, PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size,
|
||||
byte[] passphrase) {
|
||||
String volPath;
|
||||
String volName = name;
|
||||
long virtualSize = 0;
|
||||
long actualSize = 0;
|
||||
QemuObject.EncryptFormat encryptFormat = null;
|
||||
List<QemuObject> passphraseObjects = new ArrayList<>();
|
||||
|
||||
final int timeout = 0;
|
||||
QemuImgFile destFile;
|
||||
|
||||
if (StoragePoolType.RBD.equals(pool.getType())) {
|
||||
volPath = pool.getSourceDir() + File.separator + name;
|
||||
destFile = new QemuImgFile(KVMPhysicalDisk.RBDStringBuilder(pool, volPath));
|
||||
} else {
|
||||
volPath = pool.getLocalPath() + File.separator + name;
|
||||
destFile = new QemuImgFile(volPath);
|
||||
}
|
||||
|
||||
QemuImgFile destFile = new QemuImgFile(volPath);
|
||||
destFile.setFormat(format);
|
||||
destFile.setSize(size);
|
||||
Map<String, String> options = new HashMap<String, String>();
|
||||
|
|
@ -1312,11 +1357,7 @@ public class LibvirtStorageAdaptor implements StorageAdaptor {
|
|||
|
||||
|
||||
QemuImgFile srcFile;
|
||||
QemuImgFile destFile = new QemuImgFile(KVMPhysicalDisk.RBDStringBuilder(destPool.getSourceHost(),
|
||||
destPool.getSourcePort(),
|
||||
destPool.getAuthUserName(),
|
||||
destPool.getAuthSecret(),
|
||||
disk.getPath()));
|
||||
QemuImgFile destFile = new QemuImgFile(KVMPhysicalDisk.RBDStringBuilder(destPool, disk.getPath()));
|
||||
destFile.setFormat(format);
|
||||
|
||||
if (srcPool.getType() != StoragePoolType.RBD) {
|
||||
|
|
@ -1591,11 +1632,7 @@ public class LibvirtStorageAdaptor implements StorageAdaptor {
|
|||
try {
|
||||
srcFile = new QemuImgFile(sourcePath, sourceFormat);
|
||||
String rbdDestPath = destPool.getSourceDir() + "/" + name;
|
||||
String rbdDestFile = KVMPhysicalDisk.RBDStringBuilder(destPool.getSourceHost(),
|
||||
destPool.getSourcePort(),
|
||||
destPool.getAuthUserName(),
|
||||
destPool.getAuthSecret(),
|
||||
rbdDestPath);
|
||||
String rbdDestFile = KVMPhysicalDisk.RBDStringBuilder(destPool, rbdDestPath);
|
||||
destFile = new QemuImgFile(rbdDestFile, destFormat);
|
||||
|
||||
logger.debug("Starting copy from source image " + srcFile.getFileName() + " to RBD image " + rbdDestPath);
|
||||
|
|
@ -1638,9 +1675,7 @@ public class LibvirtStorageAdaptor implements StorageAdaptor {
|
|||
We let Qemu-Img do the work here. Although we could work with librbd and have that do the cloning
|
||||
it doesn't benefit us. It's better to keep the current code in place which works
|
||||
*/
|
||||
srcFile =
|
||||
new QemuImgFile(KVMPhysicalDisk.RBDStringBuilder(srcPool.getSourceHost(), srcPool.getSourcePort(), srcPool.getAuthUserName(), srcPool.getAuthSecret(),
|
||||
sourcePath));
|
||||
srcFile = new QemuImgFile(KVMPhysicalDisk.RBDStringBuilder(srcPool, sourcePath));
|
||||
srcFile.setFormat(sourceFormat);
|
||||
destFile = new QemuImgFile(destPath);
|
||||
destFile.setFormat(destFormat);
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@ public class LibvirtStoragePool implements KVMStoragePool {
|
|||
protected String authSecret;
|
||||
protected String sourceHost;
|
||||
protected int sourcePort;
|
||||
|
||||
protected String sourceDir;
|
||||
protected Map<String, String> details;
|
||||
|
||||
public LibvirtStoragePool(String uuid, String name, StoragePoolType type, StorageAdaptor adaptor, StoragePool pool) {
|
||||
this.uuid = uuid;
|
||||
|
|
@ -311,7 +311,11 @@ public class LibvirtStoragePool implements KVMStoragePool {
|
|||
|
||||
@Override
|
||||
public Map<String, String> getDetails() {
|
||||
return null;
|
||||
return this.details;
|
||||
}
|
||||
|
||||
public void setDetails(Map<String, String> details) {
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
// 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 com.cloud.hypervisor.kvm.storage;
|
||||
|
||||
import com.cloud.storage.Storage;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
class StoragePoolInformation {
|
||||
private String name;
|
||||
private String host;
|
||||
private int port;
|
||||
private String path;
|
||||
private String userInfo;
|
||||
private boolean type;
|
||||
private Storage.StoragePoolType poolType;
|
||||
private Map<String, String> details;
|
||||
|
||||
public StoragePoolInformation(String name, String host, int port, String path, String userInfo, Storage.StoragePoolType poolType, Map<String, String> details, boolean type) {
|
||||
this.name = name;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.path = path;
|
||||
this.userInfo = userInfo;
|
||||
this.type = type;
|
||||
this.poolType = poolType;
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public String getUserInfo() {
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
public boolean isType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Storage.StoragePoolType getPoolType() {
|
||||
return poolType;
|
||||
}
|
||||
|
||||
public Map<String, String> getDetails() {
|
||||
return details;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,43 +17,73 @@
|
|||
package com.cloud.hypervisor.kvm.storage;
|
||||
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class KVMPhysicalDiskTest extends TestCase {
|
||||
public class KVMPhysicalDiskTest {
|
||||
@Mock
|
||||
KVMStoragePool kvmStoragePoolMock;
|
||||
|
||||
private final String authUserName = "admin";
|
||||
|
||||
private final String authSecret = "supersecret";
|
||||
|
||||
@Test
|
||||
public void testRBDStringBuilder() {
|
||||
assertEquals(KVMPhysicalDisk.RBDStringBuilder("ceph-monitor", 8000, "admin", "supersecret", "volume1"),
|
||||
"rbd:volume1:mon_host=ceph-monitor\\:8000:auth_supported=cephx:id=admin:key=supersecret:rbd_default_format=2:client_mount_timeout=30");
|
||||
String monHosts = "ceph-monitor";
|
||||
int monPort = 8000;
|
||||
|
||||
Mockito.doReturn(monHosts).when(kvmStoragePoolMock).getSourceHost();
|
||||
Mockito.doReturn(monPort).when(kvmStoragePoolMock).getSourcePort();
|
||||
Mockito.doReturn(authUserName).when(kvmStoragePoolMock).getAuthUserName();
|
||||
Mockito.doReturn(authSecret).when(kvmStoragePoolMock).getAuthSecret();
|
||||
|
||||
String expected = "rbd:volume1:mon_host=ceph-monitor\\:8000:auth_supported=cephx:id=admin:key=supersecret:rbd_default_format=2:client_mount_timeout=30";
|
||||
String result = KVMPhysicalDisk.RBDStringBuilder(kvmStoragePoolMock, "volume1");
|
||||
|
||||
Assert.assertEquals(expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRBDStringBuilder2() {
|
||||
String monHosts = "ceph-monitor1,ceph-monitor2,ceph-monitor3";
|
||||
int monPort = 3300;
|
||||
|
||||
Mockito.doReturn(monHosts).when(kvmStoragePoolMock).getSourceHost();
|
||||
Mockito.doReturn(monPort).when(kvmStoragePoolMock).getSourcePort();
|
||||
Mockito.doReturn(authUserName).when(kvmStoragePoolMock).getAuthUserName();
|
||||
Mockito.doReturn(authSecret).when(kvmStoragePoolMock).getAuthSecret();
|
||||
|
||||
String expected = "rbd:volume1:" +
|
||||
"mon_host=ceph-monitor1\\:3300\\;ceph-monitor2\\:3300\\;ceph-monitor3\\:3300:" +
|
||||
"auth_supported=cephx:id=admin:key=supersecret:rbd_default_format=2:client_mount_timeout=30";
|
||||
String actualResult = KVMPhysicalDisk.RBDStringBuilder(monHosts, monPort, "admin", "supersecret", "volume1");
|
||||
assertEquals(expected, actualResult);
|
||||
String actualResult = KVMPhysicalDisk.RBDStringBuilder(kvmStoragePoolMock, "volume1");
|
||||
|
||||
Assert.assertEquals(expected, actualResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRBDStringBuilder3() {
|
||||
String monHosts = "[fc00:1234::1],[fc00:1234::2],[fc00:1234::3]";
|
||||
int monPort = 3300;
|
||||
|
||||
Mockito.doReturn(monHosts).when(kvmStoragePoolMock).getSourceHost();
|
||||
Mockito.doReturn(monPort).when(kvmStoragePoolMock).getSourcePort();
|
||||
Mockito.doReturn(authUserName).when(kvmStoragePoolMock).getAuthUserName();
|
||||
Mockito.doReturn(authSecret).when(kvmStoragePoolMock).getAuthSecret();
|
||||
|
||||
String expected = "rbd:volume1:" +
|
||||
"mon_host=[fc00\\:1234\\:\\:1]\\:3300\\;[fc00\\:1234\\:\\:2]\\:3300\\;[fc00\\:1234\\:\\:3]\\:3300:" +
|
||||
"auth_supported=cephx:id=admin:key=supersecret:rbd_default_format=2:client_mount_timeout=30";
|
||||
String actualResult = KVMPhysicalDisk.RBDStringBuilder(monHosts, monPort, "admin", "supersecret", "volume1");
|
||||
assertEquals(expected, actualResult);
|
||||
String actualResult = KVMPhysicalDisk.RBDStringBuilder(kvmStoragePoolMock, "volume1");
|
||||
|
||||
Assert.assertEquals(expected, actualResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -64,18 +94,18 @@ public class KVMPhysicalDiskTest extends TestCase {
|
|||
LibvirtStoragePool pool = Mockito.mock(LibvirtStoragePool.class);
|
||||
|
||||
KVMPhysicalDisk disk = new KVMPhysicalDisk(path, name, pool);
|
||||
assertEquals(disk.getName(), name);
|
||||
assertEquals(disk.getPath(), path);
|
||||
assertEquals(disk.getPool(), pool);
|
||||
assertEquals(disk.getSize(), 0);
|
||||
assertEquals(disk.getVirtualSize(), 0);
|
||||
Assert.assertEquals(disk.getName(), name);
|
||||
Assert.assertEquals(disk.getPath(), path);
|
||||
Assert.assertEquals(disk.getPool(), pool);
|
||||
Assert.assertEquals(disk.getSize(), 0);
|
||||
Assert.assertEquals(disk.getVirtualSize(), 0);
|
||||
|
||||
disk.setSize(1024);
|
||||
disk.setVirtualSize(2048);
|
||||
assertEquals(disk.getSize(), 1024);
|
||||
assertEquals(disk.getVirtualSize(), 2048);
|
||||
Assert.assertEquals(disk.getSize(), 1024);
|
||||
Assert.assertEquals(disk.getVirtualSize(), 2048);
|
||||
|
||||
disk.setFormat(PhysicalDiskFormat.RAW);
|
||||
assertEquals(disk.getFormat(), PhysicalDiskFormat.RAW);
|
||||
Assert.assertEquals(disk.getFormat(), PhysicalDiskFormat.RAW);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,11 +114,7 @@ public final class StorPoolDownloadVolumeCommandWrapper extends CommandWrapper<S
|
|||
if (isRBDPool) {
|
||||
KVMStoragePool srcPool = srcDisk.getPool();
|
||||
String rbdDestPath = srcPool.getSourceDir() + "/" + srcDisk.getName();
|
||||
srcPath = KVMPhysicalDisk.RBDStringBuilder(srcPool.getSourceHost(),
|
||||
srcPool.getSourcePort(),
|
||||
srcPool.getAuthUserName(),
|
||||
srcPool.getAuthSecret(),
|
||||
rbdDestPath);
|
||||
srcPath = KVMPhysicalDisk.RBDStringBuilder(srcPool, rbdDestPath);
|
||||
} else {
|
||||
srcPath = srcDisk.getPath();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@ import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
|
|||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.commons.lang3.EnumUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
|
@ -3158,28 +3159,41 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
|||
List<StoragePoolResponse> poolResponses = ViewResponseHelper.createStoragePoolResponse(getCustomStats, storagePools.first().toArray(new StoragePoolJoinVO[storagePools.first().size()]));
|
||||
Map<String, Long> poolUuidToIdMap = storagePools.first().stream().collect(Collectors.toMap(StoragePoolJoinVO::getUuid, StoragePoolJoinVO::getId, (a, b) -> a));
|
||||
for (StoragePoolResponse poolResponse : poolResponses) {
|
||||
Long poolId = poolUuidToIdMap.get(poolResponse.getId());
|
||||
DataStore store = dataStoreManager.getPrimaryDataStore(poolResponse.getId());
|
||||
|
||||
if (store != null) {
|
||||
DataStoreDriver driver = store.getDriver();
|
||||
if (driver != null && driver.getCapabilities() != null) {
|
||||
Map<String, String> caps = driver.getCapabilities();
|
||||
if (Storage.StoragePoolType.NetworkFilesystem.toString().equals(poolResponse.getType()) &&
|
||||
HypervisorType.VMware.toString().equals(poolResponse.getHypervisor())) {
|
||||
StoragePoolDetailVO detail = _storagePoolDetailsDao.findDetail(poolUuidToIdMap.get(poolResponse.getId()), Storage.Capability.HARDWARE_ACCELERATION.toString());
|
||||
if (detail != null) {
|
||||
caps.put(Storage.Capability.HARDWARE_ACCELERATION.toString(), detail.getValue());
|
||||
}
|
||||
}
|
||||
poolResponse.setCaps(caps);
|
||||
}
|
||||
addPoolDetailsAndCapabilities(poolResponse, store, poolId);
|
||||
}
|
||||
setPoolResponseNFSMountOptions(poolResponse, poolUuidToIdMap.get(poolResponse.getId()));
|
||||
|
||||
setPoolResponseNFSMountOptions(poolResponse, poolId);
|
||||
}
|
||||
|
||||
response.setResponses(poolResponses, storagePools.second());
|
||||
return response;
|
||||
}
|
||||
|
||||
private void addPoolDetailsAndCapabilities(StoragePoolResponse poolResponse, DataStore store, Long poolId) {
|
||||
Map<String, String> details = _storagePoolDetailsDao.listDetailsKeyPairs(store.getId(), true);
|
||||
poolResponse.setDetails(details);
|
||||
|
||||
DataStoreDriver driver = store.getDriver();
|
||||
if (ObjectUtils.anyNull(driver, driver.getCapabilities())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, String> caps = driver.getCapabilities();
|
||||
if (Storage.StoragePoolType.NetworkFilesystem.toString().equals(poolResponse.getType()) && HypervisorType.VMware.toString().equals(poolResponse.getHypervisor())) {
|
||||
StoragePoolDetailVO detail = _storagePoolDetailsDao.findDetail(poolId, Storage.Capability.HARDWARE_ACCELERATION.toString());
|
||||
if (detail != null) {
|
||||
caps.put(Storage.Capability.HARDWARE_ACCELERATION.toString(), detail.getValue());
|
||||
}
|
||||
}
|
||||
poolResponse.setCaps(caps);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Pair<List<StoragePoolJoinVO>, Integer> searchForStoragePoolsInternal(ListStoragePoolsCmd cmd) {
|
||||
ScopeType scopeType = ScopeType.validateAndGetScopeType(cmd.getScope());
|
||||
StoragePoolStatus status = StoragePoolStatus.validateAndGetStatus(cmd.getStatus());
|
||||
|
|
|
|||
|
|
@ -665,6 +665,8 @@
|
|||
"label.dark.mode": "Dark mode",
|
||||
"label.dashboard": "Dashboard",
|
||||
"label.data.disk": "Data disk",
|
||||
"label.data.pool": "Data pool",
|
||||
"label.data.pool.description": "Data pool is required when using a Ceph pool with erasure code",
|
||||
"label.data.disk.offering": "Data disk offering",
|
||||
"label.date": "Date",
|
||||
"label.datetime.filter.period": "From <b>{startDate}</b> to <b>{endDate}</b>",
|
||||
|
|
|
|||
|
|
@ -463,6 +463,8 @@
|
|||
"label.dashboard": "Dashboard",
|
||||
"label.data.disk": "Disco de dados",
|
||||
"label.data.disk.offering": "Oferta de disco adicional",
|
||||
"label.data.pool": "Data pool",
|
||||
"label.data.pool.description": "\u00c9 necess\u00e1rio informar um data pool ao utilizar um Ceph pool com erasure code",
|
||||
"label.date": "Data",
|
||||
"label.day": "Dia",
|
||||
"label.day.of.month": "Dia do m\u00eas",
|
||||
|
|
|
|||
|
|
@ -151,6 +151,13 @@
|
|||
<div>{{ $toLocaleDate(dataResource[item]) }}</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
<a-list-item v-else-if="item === 'details' && $route.meta.name === 'storagepool' && dataResource[item].rbd_default_data_pool">
|
||||
<div>
|
||||
<strong>{{ $t('label.data.pool') }}</strong>
|
||||
<br/>
|
||||
<div>{{ dataResource[item].rbd_default_data_pool }}</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</template>
|
||||
<HostInfo :resource="dataResource" v-if="$route.meta.name === 'host' && 'listHosts' in $store.getters.apis" />
|
||||
<DedicateData :resource="dataResource" v-if="dedicatedSectionActive" />
|
||||
|
|
@ -207,7 +214,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
customDisplayItems () {
|
||||
var items = ['ip4routes', 'ip6routes', 'privatemtu', 'publicmtu', 'provider']
|
||||
var items = ['ip4routes', 'ip6routes', 'privatemtu', 'publicmtu', 'provider', 'details']
|
||||
if (this.$route.meta.name === 'webhookdeliveries') {
|
||||
items.push('startdate')
|
||||
items.push('enddate')
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export default {
|
|||
fields.push('zonename')
|
||||
return fields
|
||||
},
|
||||
details: ['name', 'id', 'ipaddress', 'type', 'nfsmountopts', 'scope', 'tags', 'path', 'provider', 'hypervisor', 'overprovisionfactor', 'disksizetotal', 'disksizeallocated', 'disksizeused', 'capacityiops', 'usediops', 'clustername', 'podname', 'zonename', 'created'],
|
||||
details: ['name', 'id', 'ipaddress', 'type', 'details', 'nfsmountopts', 'scope', 'tags', 'path', 'provider', 'hypervisor', 'overprovisionfactor', 'disksizetotal', 'disksizeallocated', 'disksizeused', 'capacityiops', 'usediops', 'clustername', 'podname', 'zonename', 'created'],
|
||||
related: [{
|
||||
name: 'volume',
|
||||
title: 'label.volumes',
|
||||
|
|
|
|||
|
|
@ -370,6 +370,12 @@
|
|||
<a-form-item name="radospool" ref="radospool" :label="$t('label.rados.pool')">
|
||||
<a-input v-model:value="form.radospool" :placeholder="$t('label.rados.pool')"/>
|
||||
</a-form-item>
|
||||
<a-form-item name="datapool" ref="datapool">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.data.pool')" :tooltip="$t('label.data.pool.description')"/>
|
||||
</template>
|
||||
<a-input v-model:value="form.datapool" :placeholder="$t('label.data.pool')"/>
|
||||
</a-form-item>
|
||||
<a-form-item name="radosuser" ref="radosuser" :label="$t('label.rados.user')">
|
||||
<a-input v-model:value="form.radosuser" :placeholder="$t('label.rados.user')" />
|
||||
</a-form-item>
|
||||
|
|
@ -499,6 +505,10 @@ export default {
|
|||
powerflexGatewayUsername: [{ required: true, message: this.$t('label.required') }],
|
||||
powerflexGatewayPassword: [{ required: true, message: this.$t('label.required') }],
|
||||
powerflexStoragePool: [{ required: true, message: this.$t('label.required') }],
|
||||
radosmonitor: [{ required: true, message: this.$t('label.required') }],
|
||||
radospool: [{ required: true, message: this.$t('label.required') }],
|
||||
radosuser: [{ required: true, message: this.$t('label.required') }],
|
||||
radossecret: [{ required: true, message: this.$t('label.required') }],
|
||||
username: [{ required: true, message: this.$t('label.required') }],
|
||||
password: [{ required: true, message: this.$t('label.required') }],
|
||||
primeraURL: [{ required: true, message: this.$t('label.url') }],
|
||||
|
|
@ -845,6 +855,9 @@ export default {
|
|||
url = this.clvmURL(vg)
|
||||
} else if (values.protocol === 'RBD') {
|
||||
url = this.rbdURL(values.radosmonitor, values.radospool, values.radosuser, values.radossecret)
|
||||
if (values.datapool) {
|
||||
params['details[0].rbd_default_data_pool'] = values.datapool
|
||||
}
|
||||
} else if (values.protocol === 'vmfs') {
|
||||
path = values.vCenterDataCenter
|
||||
if (path.substring(0, 1) !== '/') {
|
||||
|
|
|
|||
|
|
@ -469,7 +469,7 @@ export default {
|
|||
title: 'label.rados.monitor',
|
||||
key: 'primaryStorageRADOSMonitor',
|
||||
placeHolder: 'message.error.rados.monitor',
|
||||
required: false,
|
||||
required: true,
|
||||
display: {
|
||||
primaryStorageProtocol: ['rbd']
|
||||
}
|
||||
|
|
@ -478,6 +478,14 @@ export default {
|
|||
title: 'label.rados.pool',
|
||||
key: 'primaryStorageRADOSPool',
|
||||
placeHolder: 'message.error.rados.pool',
|
||||
required: true,
|
||||
display: {
|
||||
primaryStorageProtocol: ['rbd']
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'label.data.pool',
|
||||
key: 'primaryStorageDataPool',
|
||||
required: false,
|
||||
display: {
|
||||
primaryStorageProtocol: ['rbd']
|
||||
|
|
@ -487,7 +495,7 @@ export default {
|
|||
title: 'label.rados.user',
|
||||
key: 'primaryStorageRADOSUser',
|
||||
placeHolder: 'message.error.rados.user',
|
||||
required: false,
|
||||
required: true,
|
||||
display: {
|
||||
primaryStorageProtocol: ['rbd']
|
||||
}
|
||||
|
|
@ -496,7 +504,7 @@ export default {
|
|||
title: 'label.rados.secret',
|
||||
key: 'primaryStorageRADOSSecret',
|
||||
placeHolder: 'message.error.rados.secret',
|
||||
required: false,
|
||||
required: true,
|
||||
display: {
|
||||
primaryStorageProtocol: ['rbd']
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1486,6 +1486,10 @@ export default {
|
|||
const rbdpool = this.prefillContent?.primaryStorageRADOSPool || ''
|
||||
const rbdid = this.prefillContent?.primaryStorageRADOSUser || ''
|
||||
const rbdsecret = this.prefillContent?.primaryStorageRADOSSecret || ''
|
||||
|
||||
if (this.prefillContent?.primaryStorageDataPool) {
|
||||
params['details[0].rbd_default_data_pool'] = this.prefillContent.primaryStorageDataPool
|
||||
}
|
||||
url = this.rbdURL(rbdmonitor, rbdpool, rbdid, rbdsecret)
|
||||
} else if (protocol === 'Linstor') {
|
||||
url = this.linstorURL(server)
|
||||
|
|
|
|||
Loading…
Reference in New Issue