Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e3d738c
adapter: add NVMETCP address type and FlashArrayConnection.nsid
Apr 20, 2026
cfa9157
flasharray: support NVMe-TCP transport
Apr 20, 2026
949618c
storage: add NVMeTCP storage pool type
Apr 20, 2026
8757a69
kvm: add MultipathNVMeOFAdapterBase and NVMeTCPAdapter
Apr 20, 2026
5ad497c
adaptive: pick NVMeTCP pool type when transport=nvme-tcp
Apr 20, 2026
393cc1c
docs: note NVMe-TCP support on the FlashArray adaptive plugin in Pend…
Apr 20, 2026
e8406e6
kvm: implement copyPhysicalDisk on MultipathNVMeOFAdapterBase
genegr Apr 22, 2026
dac30d8
kvm/flasharray: address review feedback on NVMe-TCP PR
genegr Apr 23, 2026
f830974
ui: expose NVMe-TCP transport for FlashArray primary storage
Apr 28, 2026
4610584
kvm: align MultipathNVMeOFPool with KVMStoragePool API change
Jun 14, 2026
7a57ce4
kvm: guard parseAndValidatePath against null inPath
Jun 23, 2026
83bc491
kvm/flasharray/ui: address Copilot review round 3 on NVMe-TCP PR
Aug 4, 2026
6df04e8
flasharray: tighten NVMe-TCP EUI-128 validation
Aug 4, 2026
eddb302
kvm: implement volume resize for NVMe-TCP pools
Sep 24, 2026
61753fc
server: allow online volume resize on NVMe-TCP pools
Sep 25, 2026
d2e836f
storage: recognise NVMeTCP in the managed-storage code paths
Sep 25, 2026
dd58aac
flasharray: connect NVMe-TCP volumes per host, not per host group
Sep 25, 2026
1faa7ce
flasharray: always stamp the pool address type on returned volumes
Sep 25, 2026
34edb22
kvm: align NVMe-oF adaptor stubs with the Fibre Channel adaptor
Sep 25, 2026
8d965ab
kvm: support direct download templates on NVMe-oF pools
Sep 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions PendingReleaseNotes
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,13 @@ example.ver.1 > example.ver.2:
additional key store is required. Note: attaching an encrypted RBD volume
to a running Instance requires libvirt >= 10.1.0; booting an Instance from
an encrypted RBD root disk works on older libvirt.

* Added NVMe-over-Fabrics (TCP) support to the adaptive storage framework
and the Pure Storage FlashArray plugin. Volumes on a FlashArray primary
pool can now be delivered to KVM hypervisors over NVMe-TCP instead of
Fibre Channel by setting transport=nvme-tcp on the pool's provider URL.
Volumes are identified on the host via EUI-128 NGUIDs and attached to
guests as plain block devices through the native NVMe multipath layer;
no device-mapper multipath configuration is required. A new
Storage.StoragePoolType.NVMeTCP + MultipathNVMeOFAdapterBase /
NVMeTCPAdapter on the KVM side back the new pool type.
3 changes: 2 additions & 1 deletion api/src/main/java/com/cloud/storage/Storage.java
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ public static enum StoragePoolType {
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>)
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>)
NVMeTCP(true, true, EncryptionSupport.Unsupported); // NVMe over TCP (NVMe-oF/TCP) Pool for KVM hypervisors; volumes are identified by EUI-128 NGUID (/dev/disk/by-id/nvme-eui.<eui>)

private final boolean shared;
private final boolean overProvisioning;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -616,8 +616,9 @@ private void handleVolumeMigrationFromManagedStorageToNonManagedStorage(VolumeIn
private void verifyFormatWithPoolType(ImageFormat imageFormat, StoragePoolType poolType) {
if (imageFormat != ImageFormat.VHD && imageFormat != ImageFormat.OVA && imageFormat != ImageFormat.QCOW2 &&
!(imageFormat == ImageFormat.RAW && (StoragePoolType.PowerFlex == poolType ||
StoragePoolType.FiberChannel == poolType))) {
throw new CloudRuntimeException(String.format("Only the following image types are currently supported: %s, %s, %s, %s (for PowerFlex and FiberChannel)",
StoragePoolType.FiberChannel == poolType ||
StoragePoolType.NVMeTCP == poolType))) {
throw new CloudRuntimeException(String.format("Only the following image types are currently supported: %s, %s, %s, %s (for PowerFlex, FiberChannel and NVMeTCP)",
ImageFormat.VHD.toString(), ImageFormat.OVA.toString(), ImageFormat.QCOW2.toString(), ImageFormat.RAW.toString()));
}
}
Expand Down Expand Up @@ -2796,7 +2797,8 @@ private void handleCreateTemplateFromManagedVolume(VolumeInfo volumeInfo, Templa
if (!ImageFormat.QCOW2.equals(volumeInfo.getFormat()) &&
!(ImageFormat.RAW.equals(volumeInfo.getFormat()) && (
StoragePoolType.PowerFlex == storagePoolVO.getPoolType() ||
StoragePoolType.FiberChannel == storagePoolVO.getPoolType()))) {
StoragePoolType.FiberChannel == storagePoolVO.getPoolType() ||
StoragePoolType.NVMeTCP == storagePoolVO.getPoolType()))) {
throw new CloudRuntimeException("When using managed storage, you can only create a template from a volume on KVM currently.");
}

Expand All @@ -2810,7 +2812,8 @@ private void handleCreateTemplateFromManagedVolume(VolumeInfo volumeInfo, Templa
try {
handleQualityOfServiceForVolumeMigration(volumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.MIGRATION);

if (srcVolumeDetached || StoragePoolType.PowerFlex == storagePoolVO.getPoolType() || StoragePoolType.FiberChannel == storagePoolVO.getPoolType()) {
if (srcVolumeDetached || StoragePoolType.PowerFlex == storagePoolVO.getPoolType() || StoragePoolType.FiberChannel == storagePoolVO.getPoolType()
|| StoragePoolType.NVMeTCP == storagePoolVO.getPoolType()) {
_volumeService.grantAccess(volumeInfo, hostVO, srcDataStore);
}

Expand Down Expand Up @@ -2844,7 +2847,8 @@ private void handleCreateTemplateFromManagedVolume(VolumeInfo volumeInfo, Templa
throw new CloudRuntimeException(msg + ex.getMessage(), ex);
}
finally {
if (srcVolumeDetached || StoragePoolType.PowerFlex == storagePoolVO.getPoolType() || StoragePoolType.FiberChannel == storagePoolVO.getPoolType()) {
if (srcVolumeDetached || StoragePoolType.PowerFlex == storagePoolVO.getPoolType() || StoragePoolType.FiberChannel == storagePoolVO.getPoolType()
|| StoragePoolType.NVMeTCP == storagePoolVO.getPoolType()) {
try {
_volumeService.revokeAccess(volumeInfo, hostVO, srcDataStore);
}
Expand Down Expand Up @@ -2939,7 +2943,8 @@ private Map<String, String> getSnapshotDetails(SnapshotInfo snapshotInfo) {

long snapshotId = snapshotInfo.getId();

if (storagePoolVO.getPoolType() == StoragePoolType.PowerFlex || storagePoolVO.getPoolType() == StoragePoolType.FiberChannel) {
if (storagePoolVO.getPoolType() == StoragePoolType.PowerFlex || storagePoolVO.getPoolType() == StoragePoolType.FiberChannel
|| storagePoolVO.getPoolType() == StoragePoolType.NVMeTCP) {
snapshotDetails.put(DiskTO.IQN, snapshotInfo.getPath());
} else {
snapshotDetails.put(DiskTO.IQN, getSnapshotProperty(snapshotId, DiskTO.IQN));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
import com.cloud.hypervisor.kvm.storage.MultipathNVMeOFPool;
import com.cloud.hypervisor.kvm.storage.MultipathSCSIPool;
import com.cloud.resource.CommandWrapper;
import com.cloud.resource.ResourceWrapper;
Expand Down Expand Up @@ -86,6 +87,10 @@ public Answer execute(final ResizeVolumeCommand command, final LibvirtComputingR
return handleMultipathSCSIResize(command, pool);
}

if (pool instanceof MultipathNVMeOFPool) {
return handleMultipathNVMeOFResize(command, pool);
}

if (spool.getType().equals(StoragePoolType.PowerFlex)) {
pool.connectPhysicalDisk(volumeId, null);
}
Expand Down Expand Up @@ -239,4 +244,9 @@ private Answer handleMultipathSCSIResize(ResizeVolumeCommand command, KVMStorage
((MultipathSCSIPool)pool).resize(command.getPath(), command.getInstanceName(), command.getNewSize());
return new ResizeVolumeAnswer(command, true, "");
}

private Answer handleMultipathNVMeOFResize(ResizeVolumeCommand command, KVMStoragePool pool) {
((MultipathNVMeOFPool)pool).resize(command.getPath(), command.getInstanceName(), command.getNewSize());
return new ResizeVolumeAnswer(command, true, "");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,8 @@ public Answer copyTemplateToPrimaryStorage(final CopyCommand cmd) {
StoragePoolType.PowerFlex,
StoragePoolType.Linstor,
StoragePoolType.FiberChannel,
StoragePoolType.CLVM).contains(primaryPool.getType())) {
StoragePoolType.CLVM,
StoragePoolType.NVMeTCP).contains(primaryPool.getType())) {
newTemplate.setFormat(ImageFormat.RAW);
} else {
newTemplate.setFormat(ImageFormat.QCOW2);
Expand Down Expand Up @@ -454,7 +455,8 @@ public Answer copyTemplateToPrimaryStorage(final CopyCommand cmd) {

public static String derivePath(PrimaryDataStoreTO primaryStore, DataTO destData, Map<String, String> details) {
String path = null;
if (primaryStore.getPoolType() == StoragePoolType.FiberChannel) {
if (primaryStore.getPoolType() == StoragePoolType.FiberChannel
|| primaryStore.getPoolType() == StoragePoolType.NVMeTCP) {
path = destData.getPath();
} else {
path = details != null ? details.get("managedStoreTarget") : null;
Expand Down Expand Up @@ -3513,7 +3515,8 @@ private Storage.ImageFormat getFormat(StoragePoolType poolType) {
StoragePoolType.PowerFlex,
StoragePoolType.Linstor,
StoragePoolType.FiberChannel,
StoragePoolType.CLVM).contains(poolType)) {
StoragePoolType.CLVM,
StoragePoolType.NVMeTCP).contains(poolType)) {
return ImageFormat.RAW;
} else {
return ImageFormat.QCOW2;
Expand Down
Loading