Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 18 additions & 2 deletions pkg/provider/azure/action/rhel-ai/rhelai.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package rhelai

import (
"context"
"fmt"
"strings"

Expand Down Expand Up @@ -35,15 +36,30 @@ func imageId(accelerator, version string) string {
}

func Create(mCtxArgs *maptContext.ContextArgs, args *apiRHELAI.RHELAIArgs) (err error) {
logging.Debug("Creating RHEL Server")
logging.Debug("Creating RHEL AI Server")
computeReq := *args.ComputeRequest
if len(computeReq.ComputeSizes) > 0 {
ctx := mCtxArgs.Context
if ctx == nil {
ctx = context.Background()
}
computeReq.ComputeSizes, err = data.FilterNoLocalStorageSizes(
ctx, computeReq.ComputeSizes)
if err != nil {
return err
}
if len(computeReq.ComputeSizes) == 0 {
return fmt.Errorf("no valid compute sizes: all provided sizes have NVMe-only local storage, incompatible with RHEL AI")
}
}
sharedImageID := imageId(args.Accelerator, args.Version)
if args.CustomImage != "" {
sharedImageID = imageIdFromName(args.CustomImage)
}
azureLinuxRequest :=
&azureLinux.LinuxArgs{
Prefix: args.Prefix,
ComputeRequest: args.ComputeRequest,
ComputeRequest: &computeReq,
Spot: args.Spot,
ImageRef: &data.ImageReference{
SharedImageID: sharedImageID,
Expand Down
89 changes: 83 additions & 6 deletions pkg/provider/azure/data/compute-request.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package data

import (
"context"
"os"
"regexp"
"slices"
"strconv"
Expand All @@ -12,6 +11,7 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v7"
cr "github.com/redhat-developer/mapt/pkg/provider/api/compute-request"
"github.com/redhat-developer/mapt/pkg/util/logging"
)

const (
Expand Down Expand Up @@ -66,12 +66,77 @@ func FilterComputeSizesByLocation(ctx context.Context, location *string, compute
return supportedSizes, nil
}

// FilterNoLocalStorageSizes returns only the sizes from computeSizes that have no
// NVMe-only local storage (L-series). Temp disks (MaxResourceVolumeMB > 0) are allowed
// — they are ephemeral scratch space that does not interfere with RHEL AI's OS disk.
// Sizes not found in the Azure SKU catalog (typo or restricted SKU) are logged as
// warnings and excluded.
func FilterNoLocalStorageSizes(ctx context.Context, computeSizes []string) ([]string, error) {
creds, subscriptionID, err := getCredentials()
if err != nil {
return nil, err
}
client, err := armcompute.NewResourceSKUsClient(*subscriptionID, creds, nil)
if err != nil {
return nil, err
}
pager := client.NewListPager(nil)
capabilities := make(map[string]*virtualMachine, len(computeSizes))
for pager.More() {
page, err := pager.NextPage(ctx)
if err != nil {
return nil, err
}
for _, sku := range page.Value {
if sku.ResourceType == nil || *sku.ResourceType != string(RTVirtualMachines) {
continue
}
if sku.Name == nil || !slices.Contains(computeSizes, *sku.Name) {
continue
}
if _, seen := capabilities[*sku.Name]; seen {
continue
}
if vm := resourceSKUToVirtualMachine(sku); vm != nil {
capabilities[*sku.Name] = vm
}
}
}
valid, dropped, unknown := filterNVMeStorage(computeSizes, capabilities)
for _, size := range dropped {
logging.Warnf("dropping compute size %q: has NVMe-only local storage, incompatible with RHEL AI", size)
}
for _, size := range unknown {
logging.Warnf("dropping compute size %q: not found in Azure SKU catalog (typo or restricted SKU)", size)
}
return valid, nil
}

// filterNVMeStorage classifies each size into valid (no NVMe-only local storage),
// dropped (has NVMe local storage — e.g. L-series), or unknown (absent from capabilities).
func filterNVMeStorage(computeSizes []string, capabilities map[string]*virtualMachine) (valid, dropped, unknown []string) {
for _, size := range computeSizes {
vm, ok := capabilities[size]
if !ok {
unknown = append(unknown, size)
continue
}
if vm.NvmeDiskSizeInMiB > 0 {
dropped = append(dropped, size)
} else {
valid = append(valid, size)
}
}
return valid, dropped, unknown
}

func getAzureVMSKUs(ctx context.Context, args *cr.ComputeRequestArgs) ([]string, error) {
ensureAzureEnvs()
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
return nil, err
}
subscriptionId := os.Getenv("AZURE_SUBSCRIPTION_ID")
subscriptionId := SubscriptionID()
clientFactory, err := armcompute.NewClientFactory(
subscriptionId, cred, nil)
if err != nil {
Expand Down Expand Up @@ -109,6 +174,10 @@ type virtualMachine struct {
// Spot capable
LowPriorityCapable bool
MaxResourceVolumeMB int32
// L-series VMs expose NVMe storage separately from the temp disk
NvmeDiskSizeInMiB int32
// Used by the disk-controller-type fix (PR #823) to cross-reference SKU capabilities
DiskControllerTypes []string
// IaaS or PaaS
VMDeploymentTypes []string
// Fast SSD
Expand Down Expand Up @@ -144,17 +213,17 @@ func (vm *virtualMachine) hypervGen2Supported() bool {
return slices.Contains(vm.HyperVGenerations, "V2")
}

func (vm *virtualMachine) emptyDiskSupported() bool {
return vm.MaxResourceVolumeMB == 0
func (vm *virtualMachine) noLocalStorageAttached() bool {
return vm.MaxResourceVolumeMB == 0 && vm.NvmeDiskSizeInMiB == 0
}

func (vm *virtualMachine) baseFeaturesSupported() bool {
return vm.AcceleratedNetworkingEnabled && vm.PremiumIO && vm.EncryptionAtHostSupported &&
vm.emptyDiskSupported() && vm.hypervGen2Supported()
vm.noLocalStorageAttached() && vm.hypervGen2Supported()
Comment on lines +216 to +222
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Jun 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Shared selector call sites"
rg -n -C2 'type ComputeSelector struct|NewComputeSelector\(|func \(c \*ComputeSelector\) Select\(|filterCPUsAndMemory\(|baseFeaturesSupported\(' pkg --type=go

echo
echo "RHEL AI-specific wiring"
rg -n -C2 'FilterNoLocalStorageSizes\(|func Create\(.*RHELAIArgs' pkg/provider/azure --type=go

Repository: redhat-developer/mapt

Length of output: 6358


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show getAzureVMSKUs and filterCPUsAndMemory logic to see how baseFeaturesSupported is applied.
sed -n '1,260p' pkg/provider/azure/data/compute-request.go | sed -n '1,260p' >/tmp/azure_compute_request_top.txt || true
sed -n '260,420p' pkg/provider/azure/data/compute-request.go >/tmp/azure_compute_request_mid.txt || true

# Allocation call-site
sed -n '1,120p' pkg/provider/azure/modules/allocation/allocation.go >/tmp/azure_allocation_top.txt || true

# Spot call-site
sed -n '1,140p' pkg/provider/azure/data/spot.go >/tmp/azure_spot_top.txt || true

# RHEL AI action call-site around where it filters sizes
sed -n '1,120p' pkg/provider/azure/action/rhel-ai/rhelai.go >/tmp/azure_rhelai_top.txt || true

# Print only the sections we care about (grep to keep output small)
echo "=== azure/data/compute-request.go (key parts) ==="
rg -n "func getAzureVMSKUs|func filterCPUsAndMemory|baseFeaturesSupported|noLocalStorageAttached|FilterNoLocalStorageSizes" pkg/provider/azure/data/compute-request.go
echo
echo "=== allocation.go call-site area ==="
rg -n "NewComputeSelector\\(\\)\\.Select|ComputeRequest" pkg/provider/azure/modules/allocation/allocation.go
echo
echo "=== spot.go call-site area ==="
rg -n "NewComputeSelector\\(\\)\\.Select|ComputeRequest" pkg/provider/azure/data/spot.go
echo
echo "=== rhel-ai.go call-site area ==="
rg -n "FilterNoLocalStorageSizes|ComputeSizes|ComputeRequest" pkg/provider/azure/action/rhel-ai/rhelai.go

Repository: redhat-developer/mapt

Length of output: 1386


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== pkg/provider/azure/data/compute-request.go: getAzureVMSKUs + filterCPUsAndMemory (around relevant lines) ==="
sed -n '110,190p' pkg/provider/azure/data/compute-request.go
echo
sed -n '190,260p' pkg/provider/azure/data/compute-request.go
echo
sed -n '280,370p' pkg/provider/azure/data/compute-request.go
echo

echo "=== pkg/provider/azure/modules/allocation/allocation.go (around call) ==="
sed -n '1,120p' pkg/provider/azure/modules/allocation/allocation.go
echo

echo "=== pkg/provider/azure/data/spot.go (around call) ==="
sed -n '1,130p' pkg/provider/azure/data/spot.go
echo

echo "=== pkg/provider/azure/action/rhel-ai/rhelai.go (around FilterNoLocalStorageSizes) ==="
sed -n '1,120p' pkg/provider/azure/action/rhel-ai/rhelai.go

Repository: redhat-developer/mapt

Length of output: 15965


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "All Azure call sites of NewComputeSelector().Select"
rg -n --type=go "NewComputeSelector\\(\\)\\.Select\\(" pkg/provider/azure

echo
echo "All call sites of baseFeaturesSupported()"
rg -n --type=go "baseFeaturesSupported\\(" pkg/provider/azure/data

echo
echo "All uses of filterCPUsAndMemory(...)"
rg -n --type=go "filterCPUsAndMemory\\(" pkg/provider/azure/data/compute-request.go pkg/provider/azure -S

Repository: redhat-developer/mapt

Length of output: 1090


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search Azure actions for ComputeSizes usage ==="
rg -n --type=go "ComputeRequest:\s*&?computeReq|ComputeSizes\s*:" pkg/provider/azure | head -n 200

echo
echo "=== Search for ComputeRequestArgs constructions in azure provider (top matches) ==="
rg -n --type=go "ComputeRequest:\s*.*ComputeRequestArgs|ComputeRequestArgs\s*\{" pkg/provider/azure | head -n 200

Repository: redhat-developer/mapt

Length of output: 854


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Azure: direct references to ComputeSizes on ComputeRequest ==="
rg -n --type=go "ComputeRequest\\.ComputeSizes|computeReq\\.ComputeSizes|len\\(.*ComputeSizes\\)|ComputeSizes:\\s*\\[\\]string\\{" pkg/provider/azure | head -n 200

echo
echo "=== Azure: constructions of ComputeRequestArgs (struct literals) ==="
rg -n --type=go "ComputeRequestArgs\\s*\\{" pkg | head -n 200

Repository: redhat-developer/mapt

Length of output: 933


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== Find call sites of azure/modules/allocation.Allocation ==="
rg -n --type=go "allocation\\.Allocation\\(" pkg/provider/azure | head -n 200

echo
echo "=== Find where ComputeRequestArgs is built and passed into allocation ==="
rg -n --type=go "ComputeRequestArgs|ComputeRequest:|ComputeRequest\\s*:\\s*&?computeReq" pkg/provider/azure | head -n 200

Repository: redhat-developer/mapt

Length of output: 1664


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in \
  pkg/provider/azure/action/linux/linux.go \
  pkg/provider/azure/action/windows/windows.go \
  pkg/provider/azure/action/rhel/rhel.go \
  pkg/provider/azure/action/kind/kind.go \
  pkg/provider/azure/action/aks/aks.go \
  pkg/provider/azure/action/rhel-ai/rhelai.go
do
  echo "===== $f (ComputeSizes/ComputeRequest usage) ====="
  rg -n --type=go "ComputeSizes|ComputeRequest" "$f"
done

Repository: redhat-developer/mapt

Length of output: 1544


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== ComputeRequestArgs definition (ComputeSizes field) ==="
fd -t f "compute-request*.go" pkg/provider/api/compute-request || true
rg -n --type=go "type ComputeRequestArgs" pkg/provider/api/compute-request -S || true
# Print the file with the struct
file=$(rg -n --type=go "type ComputeRequestArgs" pkg/provider/api/compute-request -S --files-with-matches | head -n 1)
if [ -n "${file:-}" ]; then
  echo "--- $file (first ~120 lines) ---"
  sed -n '1,160p' "$file"
fi

echo
echo "=== Inspect azure/action/linux/linux.go around allocation + ComputeRequest ==="
sed -n '1,140p' pkg/provider/azure/action/linux/linux.go
echo
echo "=== Inspect azure/action/windows/windows.go around allocation + ComputeRequest ==="
sed -n '1,160p' pkg/provider/azure/action/windows/windows.go
echo
echo "=== Inspect azure/action/rhel/rhel.go around allocation + ComputeRequest ==="
sed -n '1,120p' pkg/provider/azure/action/rhel/rhel.go
echo
echo "=== Inspect azure/action/kind/kind.go around allocation + ComputeRequest ==="
sed -n '1,120p' pkg/provider/azure/action/kind/kind.go

Repository: redhat-developer/mapt

Length of output: 17456


Scope issue: baseFeaturesSupported() now broadens NVMe/local-storage filtering to generic Azure auto-selection

pkg/provider/azure/data/compute-request.goComputeSelector.Select()filterCPUsAndMemory() calls vm.baseFeaturesSupported(), which now requires vm.noLocalStorageAttached() (i.e., MaxResourceVolumeMB == 0 && NvmeDiskSizeInMiB == 0). That selector is used by pkg/provider/azure/modules/allocation/allocation.go and pkg/provider/azure/data/spot.go whenever ComputeRequest.ComputeSizes is empty, and those allocation paths are shared by Azure actions like linux/windows/rhel/kind—so NVMe-backed sizes are excluded beyond pkg/provider/azure/action/rhel-ai/rhelai.go (which only filters provided ComputeSizes via FilterNoLocalStorageSizes when they’re non-empty). [Also affects ~307-323]

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/provider/azure/data/compute-request.go` around lines 215 - 221, The
current change made virtualMachine.baseFeaturesSupported() include
noLocalStorageAttached(), which unintentionally filters out NVMe/local-storage
sizes globally when ComputeRequest.ComputeSizes is empty; revert that by
removing the noLocalStorageAttached() check from
virtualMachine.baseFeaturesSupported() and instead apply
noLocalStorageAttached() only in the code paths that should exclude NVMe-backed
SKUs (e.g., when callers explicitly want no-local-storage): move the
noLocalStorageAttached() check into the selector/filter logic used by allocation
paths—update ComputeSelector.Select() / filterCPUsAndMemory() to NOT rely on
baseFeaturesSupported() for NVMe exclusion, and add explicit calls to
FilterNoLocalStorageSizes or direct noLocalStorageAttached() checks in
allocation.go and spot.go (and any callers that pass ComputeRequest.ComputeSizes
empty) so only those flows drop NVMe-backed sizes while leaving
baseFeaturesSupported() to cover generic features.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

emptyDiskSupported() has been inside baseFeaturesSupported() since the file was first created (commit c6b2de1) — filtering out local-storage VMs from auto-selection was always a global constraint, not RHEL AI-specific. The only change here is closing the NVMe loophole: L-series VMs report MaxResourceVolumeMB = 0 but expose storage under NvmeDiskSizeInMiB, so the old check missed them. No Azure action in the repo references L-series VMs, and mapt's auto-selection is designed around managed disks, so broadening the filter to catch NVMe local storage is consistent with the original intent.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rishupk, that's a clear and well-reasoned explanation — thank you for the context. You're right: the local-storage exclusion was always a global constraint on baseFeaturesSupported(), and this PR simply closes the NVMe loophole for L-series VMs that previously slipped through by reporting MaxResourceVolumeMB = 0. My concern doesn't apply here.


📝 Learnings were identified and not saved (knowledge base disabled). Enable

}

func resourceSKUToVirtualMachine(res *armcompute.ResourceSKU) *virtualMachine {
if res.ResourceType != nil && *res.ResourceType != "virtualMachines" {
if res.ResourceType != nil && *res.ResourceType != string(RTVirtualMachines) {
return nil
}
// If Machine type has any type of restriccions discard
Expand Down Expand Up @@ -219,6 +288,14 @@ func resourceSKUToVirtualMachine(res *armcompute.ResourceSKU) *virtualMachine {
return nil
}
vm.MaxResourceVolumeMB = int32(disk)
case "NvmeDiskSizeInMiB":
nvme, err := strconv.ParseUint(*capability.Value, 10, 32)
if err != nil {
return nil
}
vm.NvmeDiskSizeInMiB = int32(nvme)
case "DiskControllerTypes":
vm.DiskControllerTypes = strings.Split(*capability.Value, ",")
case "VMDeploymentTypes":
vm.VMDeploymentTypes = strings.Split(*capability.Value, ",")
default:
Expand Down
183 changes: 183 additions & 0 deletions pkg/provider/azure/data/compute-request_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package data

import (
"testing"

"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v7"
)

func ptr[T any](v T) *T { return &v }

// noLocalStorageAttached tests

func TestNoLocalStorageAttached_NoTempDiskNoNvme(t *testing.T) {
vm := &virtualMachine{MaxResourceVolumeMB: 0, NvmeDiskSizeInMiB: 0}
if !vm.noLocalStorageAttached() {
t.Error("expected true: VM with no temp disk and no NVMe should have no local storage")
}
}

func TestNoLocalStorageAttached_HasTempDisk(t *testing.T) {
vm := &virtualMachine{MaxResourceVolumeMB: 512, NvmeDiskSizeInMiB: 0}
if vm.noLocalStorageAttached() {
t.Error("expected false: VM with temp disk should have local storage")
}
}

func TestNoLocalStorageAttached_HasNvmeDisk(t *testing.T) {
// L-series bug case: MaxResourceVolumeMB=0 but NvmeDiskSizeInMiB>0
vm := &virtualMachine{MaxResourceVolumeMB: 0, NvmeDiskSizeInMiB: 5492736}
if vm.noLocalStorageAttached() {
t.Error("expected false: L-series VM with NVMe storage should have local storage")
}
}

func TestNoLocalStorageAttached_HasBoth(t *testing.T) {
vm := &virtualMachine{MaxResourceVolumeMB: 512, NvmeDiskSizeInMiB: 5492736}
if vm.noLocalStorageAttached() {
t.Error("expected false: VM with both temp disk and NVMe should have local storage")
}
}

// resourceSKUToVirtualMachine parsing tests

func TestResourceSKUToVirtualMachine_ParsesNvmeDiskSizeInMiB(t *testing.T) {
sku := &armcompute.ResourceSKU{
ResourceType: ptr("virtualMachines"),
Name: ptr("Standard_L8aos_v4"),
Family: ptr("standardLasv4Family"),
Capabilities: []*armcompute.ResourceSKUCapabilities{
{Name: ptr("NvmeDiskSizeInMiB"), Value: ptr("5492736")},
},
}
vm := resourceSKUToVirtualMachine(sku)
if vm == nil {
t.Fatal("expected non-nil virtualMachine")
}
if vm.NvmeDiskSizeInMiB != 5492736 {
t.Errorf("NvmeDiskSizeInMiB: got %d, want 5492736", vm.NvmeDiskSizeInMiB)
}
}

func TestResourceSKUToVirtualMachine_ParsesDiskControllerTypes(t *testing.T) {
sku := &armcompute.ResourceSKU{
ResourceType: ptr("virtualMachines"),
Name: ptr("Standard_L8aos_v4"),
Family: ptr("standardLasv4Family"),
Capabilities: []*armcompute.ResourceSKUCapabilities{
{Name: ptr("DiskControllerTypes"), Value: ptr("NVMe,SCSI")},
},
}
vm := resourceSKUToVirtualMachine(sku)
if vm == nil {
t.Fatal("expected non-nil virtualMachine")
}
if len(vm.DiskControllerTypes) != 2 {
t.Fatalf("DiskControllerTypes: got %v, want [NVMe SCSI]", vm.DiskControllerTypes)
}
if vm.DiskControllerTypes[0] != "NVMe" || vm.DiskControllerTypes[1] != "SCSI" {
t.Errorf("DiskControllerTypes: got %v, want [NVMe SCSI]", vm.DiskControllerTypes)
}
}

func TestResourceSKUToVirtualMachine_NvmeDiskSizeDefaultsToZero(t *testing.T) {
sku := &armcompute.ResourceSKU{
ResourceType: ptr("virtualMachines"),
Name: ptr("Standard_D8as_v5"),
Family: ptr("standardDasv5Family"),
Capabilities: []*armcompute.ResourceSKUCapabilities{
{Name: ptr("MaxResourceVolumeMB"), Value: ptr("307200")},
},
}
vm := resourceSKUToVirtualMachine(sku)
if vm == nil {
t.Fatal("expected non-nil virtualMachine")
}
if vm.NvmeDiskSizeInMiB != 0 {
t.Errorf("NvmeDiskSizeInMiB: got %d, want 0 for non-NVMe SKU", vm.NvmeDiskSizeInMiB)
}
}

// filterNVMeStorage tests

func TestFilterNVMeStorage_DropsNvmeSizes(t *testing.T) {
capabilities := map[string]*virtualMachine{
"Standard_D8as_v5": {MaxResourceVolumeMB: 0, NvmeDiskSizeInMiB: 0},
"Standard_L8aos_v4": {MaxResourceVolumeMB: 0, NvmeDiskSizeInMiB: 5492736},
}
got, dropped, unknown := filterNVMeStorage([]string{"Standard_D8as_v5", "Standard_L8aos_v4"}, capabilities)
if len(got) != 1 || got[0] != "Standard_D8as_v5" {
t.Errorf("filtered: got %v, want [Standard_D8as_v5]", got)
}
if len(dropped) != 1 || dropped[0] != "Standard_L8aos_v4" {
t.Errorf("dropped: got %v, want [Standard_L8aos_v4]", dropped)
}
if len(unknown) != 0 {
t.Errorf("unknown: got %v, want []", unknown)
}
}

func TestFilterNVMeStorage_AllowsTempDiskSizes(t *testing.T) {
capabilities := map[string]*virtualMachine{
"Standard_NC64as_T4_v3": {MaxResourceVolumeMB: 32768, NvmeDiskSizeInMiB: 0},
}
got, dropped, unknown := filterNVMeStorage([]string{"Standard_NC64as_T4_v3"}, capabilities)
if len(got) != 1 {
t.Errorf("filtered: got %v, want [Standard_NC64as_T4_v3]", got)
}
if len(dropped) != 0 {
t.Errorf("dropped: got %v, want []", dropped)
}
if len(unknown) != 0 {
t.Errorf("unknown: got %v, want []", unknown)
}
}

func TestFilterNVMeStorage_PassesCleanSizes(t *testing.T) {
capabilities := map[string]*virtualMachine{
"Standard_D8as_v5": {MaxResourceVolumeMB: 0, NvmeDiskSizeInMiB: 0},
}
got, dropped, unknown := filterNVMeStorage([]string{"Standard_D8as_v5"}, capabilities)
if len(got) != 1 {
t.Errorf("filtered: got %v, want [Standard_D8as_v5]", got)
}
if len(dropped) != 0 {
t.Errorf("dropped: got %v, want []", dropped)
}
if len(unknown) != 0 {
t.Errorf("unknown: got %v, want []", unknown)
}
}

func TestFilterNVMeStorage_ReportsUnknownSizes(t *testing.T) {
capabilities := map[string]*virtualMachine{
"Standard_D8as_v5": {MaxResourceVolumeMB: 0, NvmeDiskSizeInMiB: 0},
}
got, dropped, unknown := filterNVMeStorage(
[]string{"Standard_D8as_v5", "Standard_Typo_v99"}, capabilities)
if len(got) != 1 || got[0] != "Standard_D8as_v5" {
t.Errorf("filtered: got %v, want [Standard_D8as_v5]", got)
}
if len(dropped) != 0 {
t.Errorf("dropped: got %v, want []", dropped)
}
if len(unknown) != 1 || unknown[0] != "Standard_Typo_v99" {
t.Errorf("unknown: got %v, want [Standard_Typo_v99]", unknown)
}
}

// baseFeaturesSupported regression: L-series must be rejected

func TestBaseFeaturesSupported_LSeriesWithNvmeIsRejected(t *testing.T) {
vm := &virtualMachine{
MaxResourceVolumeMB: 0,
NvmeDiskSizeInMiB: 5492736,
AcceleratedNetworkingEnabled: true,
PremiumIO: true,
EncryptionAtHostSupported: true,
HyperVGenerations: []string{"V1", "V2"},
}
if vm.baseFeaturesSupported() {
t.Error("expected false: L-series VM with NVMe storage must not pass baseFeaturesSupported")
}
}
Loading
Loading