diff --git a/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java index 676803ea86b6..51efb6d42cb1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java @@ -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 details; + public Map getCaps() { return caps; } @@ -407,4 +411,12 @@ public Boolean getManaged() { public void setManaged(Boolean managed) { this.managed = managed; } + + public Map getDetails() { + return details; + } + + public void setDetails(Map details) { + this.details = details; + } } diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/provider/DefaultHostListener.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/provider/DefaultHostListener.java index 1afc1a68b44e..331b1f3ce5b9 100644 --- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/provider/DefaultHostListener.java +++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/provider/DefaultHostListener.java @@ -43,7 +43,6 @@ 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 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 @@ private NicTO createNicTOFromNetworkAndOffering(NetworkVO networkVO, NetworkOffe @Override public boolean hostConnect(long hostId, long poolId) throws StorageConflictException { StoragePool pool = (StoragePool) this.dataStoreMgr.getDataStore(poolId, DataStoreRole.Primary); - Pair, Boolean> nfsMountOpts = storageManager.getStoragePoolNFSMountOpts(pool, null); + Map detailsMap = storagePoolDetailsDao.listDetailsKeyPairs(poolId); + Map 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()); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreatePrivateTemplateFromVolumeCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreatePrivateTemplateFromVolumeCommandWrapper.java index de35a1251bba..9fb282866cbd 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreatePrivateTemplateFromVolumeCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreatePrivateTemplateFromVolumeCommandWrapper.java @@ -108,9 +108,7 @@ public Answer execute(final CreatePrivateTemplateFromVolumeCommand command, fina } 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"); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVolumesOnStorageCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVolumesOnStorageCommandWrapper.java index 821a80f5ccac..0f0c6488bb3a 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVolumesOnStorageCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVolumesOnStorageCommandWrapper.java @@ -161,11 +161,7 @@ private Map getDiskFileInfo(KVMStoragePool pool, KVMPhysicalDisk 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); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/IscsiAdmStorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/IscsiAdmStorageAdaptor.java index 6185c69032fb..ba689d5107f7 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/IscsiAdmStorageAdaptor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/IscsiAdmStorageAdaptor.java @@ -410,9 +410,7 @@ public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk srcDisk, String destVolu 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()); } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDisk.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDisk.java index 9d9a6415e27f..ab02e16603d5 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDisk.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDisk.java @@ -23,6 +23,7 @@ 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 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 static String RBDStringBuilder(String monHost, int monPort, String authUs 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"; diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java index e27547acbb27..ed389a1cf738 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java @@ -53,28 +53,6 @@ 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 details; - - public StoragePoolInformation(String name, String host, int port, String path, String userInfo, StoragePoolType poolType, Map 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 _storagePools = new ConcurrentHashMap(); private final Map _storageMapper = new HashMap(); @@ -303,14 +281,33 @@ public KVMStoragePool getStoragePool(StoragePoolType type, String uuid, boolean } 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 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; 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 92e4570170e3..5ea3f38a850a 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 @@ -661,9 +661,7 @@ public Answer createTemplateFromVolume(final CopyCommand cmd) { } 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"); @@ -1010,9 +1008,7 @@ public Answer backupSnapshot(final CopyCommand cmd) { 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); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java index f3731459f89b..e93f82ac4dd8 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java @@ -960,17 +960,55 @@ public boolean deleteStoragePool(String uuid) { } } + /** + * Creates a physical disk depending on the {@link StoragePoolType}: + *
    + *
  • + * {@link StoragePoolType#RBD} + *
      + *
    • + * 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[])} + *
    • + *
    • + * Otherwise, utilize Libvirt to create the physical disk through the method + * {@link LibvirtStorageAdaptor#createPhysicalDiskByLibVirt(String, KVMStoragePool, PhysicalDiskFormat, Storage.ProvisioningType, long)} + *
    • + *
    + *
  • + *
  • + * {@link StoragePoolType#NetworkFilesystem} and {@link StoragePoolType#Filesystem} + *
      + *
    • + * 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[])} + *
    • + *
    • + * 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)} + *
    • + *
    + *
  • + *
  • + * For the rest of the {@link StoragePoolType} types, utilizes the Libvirt method + * {@link LibvirtStorageAdaptor#createPhysicalDiskByLibVirt(String, KVMStoragePool, PhysicalDiskFormat, Storage.ProvisioningType, long)} + *
  • + *
+ */ @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 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 @@ private KVMPhysicalDisk createPhysicalDiskByLibVirt(String name, KVMStoragePool } - 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 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 options = new HashMap(); @@ -1312,11 +1357,7 @@ private KVMPhysicalDisk createDiskFromTemplateOnRBD(KVMPhysicalDisk template, 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 @@ to support snapshots(backuped) as qcow2 files. */ 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 @@ to support snapshots(backuped) as qcow2 files. */ 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); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java index 8e5af7c613d5..ab39f7bc6ffd 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java @@ -56,8 +56,8 @@ public class LibvirtStoragePool implements KVMStoragePool { protected String authSecret; protected String sourceHost; protected int sourcePort; - protected String sourceDir; + protected Map details; public LibvirtStoragePool(String uuid, String name, StoragePoolType type, StorageAdaptor adaptor, StoragePool pool) { this.uuid = uuid; @@ -311,7 +311,11 @@ public boolean supportsConfigDriveIso() { @Override public Map getDetails() { - return null; + return this.details; + } + + public void setDetails(Map details) { + this.details = details; } @Override diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/StoragePoolInformation.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/StoragePoolInformation.java new file mode 100644 index 000000000000..f330c9f24f05 --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/StoragePoolInformation.java @@ -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 details; + + public StoragePoolInformation(String name, String host, int port, String path, String userInfo, Storage.StoragePoolType poolType, Map 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 getDetails() { + return details; + } +} diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDiskTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDiskTest.java index 187565c06025..098f3e1b8bdb 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDiskTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDiskTest.java @@ -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 void testAttributes() { 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); } } diff --git a/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolDownloadVolumeCommandWrapper.java b/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolDownloadVolumeCommandWrapper.java index 37284b597d2c..1679e646e189 100644 --- a/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolDownloadVolumeCommandWrapper.java +++ b/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/StorPoolDownloadVolumeCommandWrapper.java @@ -114,11 +114,7 @@ public CopyCmdAnswer execute(final StorPoolDownloadVolumeCommand cmd, final Libv 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(); } diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 631cdc5b4030..ffc3ebefa426 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -164,6 +164,7 @@ 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; @@ -3138,28 +3139,41 @@ private ListResponse createStoragesPoolResponse(Pair poolResponses = ViewResponseHelper.createStoragePoolResponse(getCustomStats, storagePools.first().toArray(new StoragePoolJoinVO[storagePools.first().size()])); Map 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 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 details = _storagePoolDetailsDao.listDetailsKeyPairs(store.getId(), true); + poolResponse.setDetails(details); + + DataStoreDriver driver = store.getDriver(); + if (ObjectUtils.anyNull(driver, driver.getCapabilities())) { + return; + } + + Map 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, Integer> searchForStoragePoolsInternal(ListStoragePoolsCmd cmd) { ScopeType scopeType = ScopeType.validateAndGetScopeType(cmd.getScope()); StoragePoolStatus status = StoragePoolStatus.validateAndGetStatus(cmd.getStatus()); diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 428d0eb90f18..61e502d98492 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -651,6 +651,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 {startDate} to {endDate}", diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json index 511c8714daa8..4ed108b29eed 100644 --- a/ui/public/locales/pt_BR.json +++ b/ui/public/locales/pt_BR.json @@ -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", diff --git a/ui/src/components/view/DetailsTab.vue b/ui/src/components/view/DetailsTab.vue index 3622f87e67d8..bbabd8c801d8 100644 --- a/ui/src/components/view/DetailsTab.vue +++ b/ui/src/components/view/DetailsTab.vue @@ -151,6 +151,13 @@
{{ $toLocaleDate(dataResource[item]) }}
+ +
+ {{ $t('label.data.pool') }} +
+
{{ dataResource[item].rbd_default_data_pool }}
+
+
@@ -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') diff --git a/ui/src/config/section/infra/primaryStorages.js b/ui/src/config/section/infra/primaryStorages.js index 826ffc7422b5..1b0e5ef1634d 100644 --- a/ui/src/config/section/infra/primaryStorages.js +++ b/ui/src/config/section/infra/primaryStorages.js @@ -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', diff --git a/ui/src/views/infra/AddPrimaryStorage.vue b/ui/src/views/infra/AddPrimaryStorage.vue index 1420d7b6ff1b..01532f3071b8 100644 --- a/ui/src/views/infra/AddPrimaryStorage.vue +++ b/ui/src/views/infra/AddPrimaryStorage.vue @@ -370,6 +370,12 @@ + + + + @@ -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) !== '/') { diff --git a/ui/src/views/infra/zone/ZoneWizardAddResources.vue b/ui/src/views/infra/zone/ZoneWizardAddResources.vue index 00811ed3a109..76b90bd3661f 100644 --- a/ui/src/views/infra/zone/ZoneWizardAddResources.vue +++ b/ui/src/views/infra/zone/ZoneWizardAddResources.vue @@ -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'] } diff --git a/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue b/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue index 01006cd0c722..a0d7c142a445 100644 --- a/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue +++ b/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue @@ -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)