Skip to content
Open
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
1 change: 1 addition & 0 deletions regions.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const (
CapabilityDistributedPlans RegionCapability = "Distributed Plans"
CapabilityEdgePlans RegionCapability = "Edge Plans"
CapabilityGPU RegionCapability = "GPU Linodes"
CapabilityGPUDirectRDMA RegionCapability = "GPUDirect RDMA"
CapabilityKubernetesEnterprise RegionCapability = "Kubernetes Enterprise"
CapabilityKubernetesEnterpriseBYOVPC RegionCapability = "Kubernetes Enterprise BYO VPC"
CapabilityKubernetesEnterpriseDualStack RegionCapability = "Kubernetes Enterprise Dual Stack"
Expand Down
647 changes: 397 additions & 250 deletions test/integration/fixtures/TestVPC_CreateGet.yaml

Large diffs are not rendered by default.

627 changes: 386 additions & 241 deletions test/integration/fixtures/TestVPC_Create_Invalid.yaml

Large diffs are not rendered by default.

654 changes: 404 additions & 250 deletions test/integration/fixtures/TestVPC_List.yaml

Large diffs are not rendered by default.

705 changes: 427 additions & 278 deletions test/integration/fixtures/TestVPC_ListAllIPAddresses.yaml

Large diffs are not rendered by default.

700 changes: 424 additions & 276 deletions test/integration/fixtures/TestVPC_ListAllIPv6Addresses.yaml

Large diffs are not rendered by default.

707 changes: 428 additions & 279 deletions test/integration/fixtures/TestVPC_ListIPAddresses.yaml

Large diffs are not rendered by default.

700 changes: 424 additions & 276 deletions test/integration/fixtures/TestVPC_ListIPv6Addresses.yaml

Large diffs are not rendered by default.

639 changes: 393 additions & 246 deletions test/integration/fixtures/TestVPC_Subnet_Create.yaml

Large diffs are not rendered by default.

646 changes: 396 additions & 250 deletions test/integration/fixtures/TestVPC_Subnet_Create_Invalid_data.yaml

Large diffs are not rendered by default.

651 changes: 399 additions & 252 deletions test/integration/fixtures/TestVPC_Subnet_Update.yaml

Large diffs are not rendered by default.

651 changes: 399 additions & 252 deletions test/integration/fixtures/TestVPC_Subnet_Update_Invalid_Label.yaml

Large diffs are not rendered by default.

767 changes: 767 additions & 0 deletions test/integration/fixtures/TestVPC_Subnet_WithRDMAType.yaml

Large diffs are not rendered by default.

652 changes: 401 additions & 251 deletions test/integration/fixtures/TestVPC_Update_Invalid.yaml

Large diffs are not rendered by default.

772 changes: 772 additions & 0 deletions test/integration/fixtures/TestVPC_WithRDMAType.yaml

Large diffs are not rendered by default.

556 changes: 556 additions & 0 deletions test/integration/fixtures/TestVPC_WithRDMATypeAndIPv6_Fail.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion test/integration/lke_clusters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func TestLKECluster_Enterprise_BYOVPC_smoke(t *testing.T) {
require.Greater(t, len(regions), 0, "Error getting regions with required capabilities")

region := regions[0]
vpc, vpcTeardown, err := createVPC(t, client, []vpcModifier{func(l *linodego.Client, options *linodego.VPCCreateOptions) {
vpc, _, vpcTeardown, err := createVPC(t, client, []vpcModifier{func(l *linodego.Client, options *linodego.VPCCreateOptions) {
options.Region = region
options.IPv6 = []linodego.VPCCreateOptionsIPv6{
{
Expand Down
45 changes: 40 additions & 5 deletions test/integration/vpc_subnet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/linode/linodego/v2"
. "github.com/linode/linodego/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

const (
Expand Down Expand Up @@ -198,6 +199,7 @@ func TestVPC_Subnet_Create(t *testing.T) {
vpcSubnetCheck(vpcSubnet, t)
opts := vpcSubnet.GetCreateOptions()
vpcSubnetCreateOptionsCheck(&opts, vpcSubnet, t)
assert.Equal(t, linodego.VPCTypeRegular, vpcSubnet.VPCType, "Expected VPC type to be regular")
}

func TestVPC_Subnet_Update(t *testing.T) {
Expand Down Expand Up @@ -249,14 +251,14 @@ func TestVPC_Subnet_List(t *testing.T) {
}

func TestVPC_Subnet_Create_Invalid_data(t *testing.T) {
client, vpc, teardown, err := setupVPC(t, "fixtures/TestVPC_Subnet_Create_Invalid_data")
client, vpc, _, teardown, err := setupVPC(t, "fixtures/TestVPC_Subnet_Create_Invalid_data")
defer teardown()
if err != nil {
t.Error(formatVPCSubnetError(err, "setting up", nil, nil))
}

createOpts := linodego.VPCSubnetCreateOptions{
Label: "linodego-vpc-test_invalid_label" + getUniqueText(),
Label: "linodego-vpc-test_invalid_label!" + getUniqueText(),
IPv4: TestSubnetIPv4,
}
_, err = client.CreateVPCSubnet(context.Background(), createOpts, vpc.ID)
Expand All @@ -265,7 +267,7 @@ func TestVPC_Subnet_Create_Invalid_data(t *testing.T) {
if e.Code != 400 {
t.Errorf("should have received a 400 Code with invalid label, got %v", e.Code)
}
expectedErrorMessage := "Label must include only ASCII letters, numbers, and dashes"
expectedErrorMessage := "Must only use ASCII letters, numbers, and underscores"
if !strings.Contains(e.Message, expectedErrorMessage) {
t.Errorf("Wrong error message displayed should have contained, %s", expectedErrorMessage)
}
Expand All @@ -282,7 +284,7 @@ func TestVPC_Subnet_Update_Invalid_data(t *testing.T) {
opts := vpcSubnet.GetUpdateOptions()
vpcSubnetUpdateOptionsCheck(&opts, vpcSubnet, t)

opts.Label = "invalid_label"
opts.Label = "invalid_label!"
_, err = client.UpdateVPCSubnet(
context.Background(),
vpc.ID,
Expand All @@ -295,7 +297,7 @@ func TestVPC_Subnet_Update_Invalid_data(t *testing.T) {
if e.Code != 400 {
t.Errorf("should have received a 400 Code with invalid label, got %v", e.Code)
}
expectedErrorMessage := "Label must include only ASCII letters, numbers, and dashes"
expectedErrorMessage := "Must only use ASCII letters, numbers, and underscores"
if !strings.Contains(e.Message, expectedErrorMessage) {
t.Errorf("Wrong error message displayed should have contained, %s", expectedErrorMessage)
}
Expand Down Expand Up @@ -370,3 +372,36 @@ func TestVPC_Subnet_WithNodeBalancer(t *testing.T) {
assert.Equal(t, "192.168.0.64/30", refreshedSubnet.Nodebalancers[0].Ipv4Range, "expected matching ipv4 range")
assert.Equal(t, 0, len(refreshedSubnet.Nodebalancers[0].Ipv6Ranges), "expected 0 ipv6 ranges")
}

func TestVPC_Subnet_WithRDMAType(t *testing.T) {
client, vpc, vpcSubnet, teardown, err := setupVPCWithSubnet(
t,
"fixtures/TestVPC_Subnet_WithRDMAType",
func(client *linodego.Client, opts *linodego.VPCCreateOptions) {
// GPUDirect RDMA capability not available for now
// opts.Region = getRegionsWithCaps(t, client, []RegionCapability{linodego.CapabilityVPCs, linodego.CapabilityGPUDirectRDMA})[0]
opts.Region = getRegionsWithCaps(t, client, []RegionCapability{linodego.CapabilityVPCs})[0]
opts.VPCType = linodego.Pointer(linodego.VPCTypeRDMA)
},
)
defer teardown()
require.NoError(t, err, "Error creating RDMA VPC with subnet")
assert.Equal(t, linodego.VPCTypeRDMA, vpcSubnet.VPCType, "Expected VPC subnet type to be RDMA")

vpcSubnet, err = client.GetVPCSubnet(context.Background(), vpc.ID, vpcSubnet.ID)
require.NoError(t, err, "Error retrieving VPC subnet")
assert.Equal(t, linodego.VPCTypeRDMA, vpcSubnet.VPCType, "Expected VPC subnet type to be RDMA")

subnets, err := client.ListVPCSubnets(context.Background(), vpc.ID, nil)
require.NoError(t, err, "Error listing VPC subnets")

var found *linodego.VPCSubnet
for idx := range subnets {
if subnets[idx].ID == vpcSubnet.ID {
found = &subnets[idx]
break
}
}
require.NotNil(t, found, "VPC subnet not found in list")
assert.Equal(t, linodego.VPCTypeRDMA, found.VPCType, "Expected VPC subnet type to be RDMA")
}
127 changes: 98 additions & 29 deletions test/integration/vpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/linode/linodego/v2"
. "github.com/linode/linodego/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand All @@ -32,7 +33,7 @@ func formatVPCError(err error, action string, vpcID *int) error {
)
}

func createVPC(t *testing.T, client *linodego.Client, vpcModifier ...vpcModifier) (*linodego.VPC, func(), error) {
func createVPC(t *testing.T, client *linodego.Client, vpcModifier ...vpcModifier) (*linodego.VPC, linodego.VPCCreateOptions, func(), error) {
t.Helper()
createOpts := linodego.VPCCreateOptions{
Label: "go-test-vpc-" + getUniqueText(),
Expand All @@ -53,13 +54,13 @@ func createVPC(t *testing.T, client *linodego.Client, vpcModifier ...vpcModifier
t.Error(formatVPCError(err, "deleting", &vpc.ID))
}
}
return vpc, teardown, err
return vpc, createOpts, teardown, err
}

func createVPC_invalid_label(t *testing.T, client *linodego.Client) error {
t.Helper()
createOpts := linodego.VPCCreateOptions{
Label: "gotest_vpc_invalid_label" + getUniqueText(),
Label: "gotest_vpc_invalid_label!" + getUniqueText(),
Region: getRegionsWithCaps(t, client, []linodego.RegionCapability{CapabilityVPCs})[0],
}
_, err := client.CreateVPC(context.Background(), createOpts)
Expand All @@ -70,19 +71,20 @@ func createVPC_invalid_label(t *testing.T, client *linodego.Client) error {
func setupVPC(t *testing.T, fixturesYaml string) (
*linodego.Client,
*linodego.VPC,
linodego.VPCCreateOptions,
func(),
error,
) {
t.Helper()
client, fixtureTeardown := createTestClient(t, fixturesYaml)

vpc, vpcTeardown, err := createVPC(t, client)
vpc, createOpts, vpcTeardown, err := createVPC(t, client)

teardown := func() {
vpcTeardown()
fixtureTeardown()
}
return client, vpc, teardown, err
return client, vpc, createOpts, teardown, err
}

func vpcCheck(vpc *linodego.VPC, t *testing.T) {
Expand All @@ -98,20 +100,20 @@ func vpcCreateOptionsCheck(
vpc *linodego.VPC,
t *testing.T,
) {
good := (opts.Description == vpc.Description &&
opts.Label == vpc.Label &&
opts.Region == vpc.Region &&
len(opts.Subnets) == len(vpc.Subnets))
require.NotEmpty(t, vpc.Label, "VPC label should not be empty")
require.Equal(t, opts.Description, vpc.Description, "VPC description mismatch")
require.Equal(t, opts.Region, vpc.Region, "VPC region mismatch")
require.Equal(t, len(opts.Subnets), len(vpc.Subnets), "VPC subnet count mismatch")
Comment thread
yec-akamai marked this conversation as resolved.

for i := 0; i < minInt(len(opts.Subnets), len(vpc.Subnets)); i++ {
good = good && (opts.Subnets[i].IPv4 == vpc.Subnets[i].IPv4 &&
opts.Subnets[i].Label == vpc.Subnets[i].Label)
expectedVPCType := VPCTypeRegular
if opts.VPCType != nil {
expectedVPCType = *opts.VPCType
}
require.Equal(t, expectedVPCType, vpc.VPCType, "VPC type mismatch")

if !good {
t.Error(
"the VPC instance and the VPC creation options instance are mismatched",
)
for i := 0; i < minInt(len(opts.Subnets), len(vpc.Subnets)); i++ {
require.Equal(t, opts.Subnets[i].IPv4, vpc.Subnets[i].IPv4, "VPC subnet IPv4 mismatch at index %d", i)
require.Equal(t, opts.Subnets[i].Label, vpc.Subnets[i].Label, "VPC subnet label mismatch at index %d", i)
}
}

Expand All @@ -126,19 +128,19 @@ func vpcUpdateOptionsCheck(
}

func TestVPC_CreateGet_smoke(t *testing.T) {
client, vpc, teardown, err := setupVPC(t, "fixtures/TestVPC_CreateGet")
client, vpc, createOpts, teardown, err := setupVPC(t, "fixtures/TestVPC_CreateGet")
defer teardown()
if err != nil {
t.Error(formatVPCError(err, "setting up", nil))
}
vpcCheck(vpc, t)
opts := vpc.GetCreateOptions()
vpcCreateOptionsCheck(&opts, vpc, t)
client.GetVPC(context.TODO(), vpc.ID)
vpcCreateOptionsCheck(&createOpts, vpc, t)
_, err = client.GetVPC(context.TODO(), vpc.ID)
require.NoError(t, err)
}

func TestVPC_Update(t *testing.T) {
client, vpc, teardown, err := setupVPC(t, "fixtures/TestVPC_Update")
client, vpc, _, teardown, err := setupVPC(t, "fixtures/TestVPC_Update")
Comment thread
yec-akamai marked this conversation as resolved.
defer teardown()
if err != nil {
t.Error(formatVPCError(err, "setting up", nil))
Expand All @@ -161,7 +163,7 @@ func TestVPC_Update(t *testing.T) {
}

func TestVPC_List(t *testing.T) {
client, vpc, teardown, err := setupVPC(t, "fixtures/TestVPC_List")
client, vpc, _, teardown, err := setupVPC(t, "fixtures/TestVPC_List")
defer teardown()
if err != nil {
t.Error(formatVPCError(err, "setting up", nil))
Expand Down Expand Up @@ -190,19 +192,20 @@ func TestVPC_Create_Invalid_data(t *testing.T) {
defer teardown()
err := createVPC_invalid_label(t, client)

e, _ := err.(*Error)
var e *linodego.Error
require.ErrorAsf(t, err, &e, "Expected error to be of type *linodego.Error, got: %T", err)

if e.Code != 400 {
t.Errorf("should have received a 400 Code with invalid label, got %v", e.Code)
}
expectedErrorMessage := "Label must include only ASCII letters, numbers, and dashes"
expectedErrorMessage := "Must only use ASCII letters, numbers, and underscores"
if !strings.Contains(e.Message, expectedErrorMessage) {
t.Errorf("Wrong error message displayed should have contained, %s", expectedErrorMessage)
}
}

func TestVPC_Update_Invalid_data(t *testing.T) {
client, vpc, teardown, err := setupVPC(t, "fixtures/TestVPC_Update_Invalid")
client, vpc, _, teardown, err := setupVPC(t, "fixtures/TestVPC_Update_Invalid")
Comment thread
yec-akamai marked this conversation as resolved.
defer teardown()
if err != nil {
t.Error(formatVPCError(err, "setting up", nil))
Expand All @@ -213,19 +216,20 @@ func TestVPC_Update_Invalid_data(t *testing.T) {
vpcUpdateOptionsCheck(&opts, vpc, t)

updatedDescription := "updated description"
updatedLabel := "updated_invalid_label"
updatedLabel := "updated-invalid_label!"

opts.Description = updatedDescription
opts.Label = updatedLabel

Comment thread
yec-akamai marked this conversation as resolved.
_, err = client.UpdateVPC(context.Background(), vpc.ID, opts)

e, _ := err.(*Error)
var e *linodego.Error
require.ErrorAsf(t, err, &e, "Expected error to be of type *linodego.Error, got: %T", err)

if e.Code != 400 {
t.Errorf("should have received a 400 Code with invalid label, got %v", e.Code)
}
expectedErrorMessage := "Label must include only ASCII letters, numbers, and dashes"
expectedErrorMessage := "Must only use ASCII letters, numbers, and underscores"
if !strings.Contains(e.Message, expectedErrorMessage) {
t.Errorf("Wrong error message displayed should have contained, %s", expectedErrorMessage)
}
Expand Down Expand Up @@ -343,7 +347,7 @@ func TestVPC_IPv4Ranges(t *testing.T) {
ctx := context.Background()
ipv4Range := "10.118.0.0/20"

vpc, vpcTeardown, err := createVPC(
vpc, _, vpcTeardown, err := createVPC(
t,
client,
func(_ *linodego.Client, options *linodego.VPCCreateOptions) {
Expand Down Expand Up @@ -418,3 +422,68 @@ func requireIPv4Contains(t *testing.T, ranges []linodego.VPCIPv4Range, wantRange
require.Failf(t, "IPv4 range not found",
"%s: expected IPv4 range %s in %+v", context, wantRange, ranges)
}

func TestVPC_WithRDMAType(t *testing.T) {
client, teardown := createTestClient(t, "fixtures/TestVPC_WithRDMAType")
defer teardown()

vpc, createOpts, vpcTeardown, err := createVPC(t, client, []vpcModifier{func(l *linodego.Client, options *linodego.VPCCreateOptions) {
// GPUDirect RDMA capability not available for now
// options.Region = getRegionsWithCaps(t, client, []RegionCapability{linodego.CapabilityVPCs, linodego.CapabilityGPUDirectRDMA})[0]
options.Region = getRegionsWithCaps(t, client, []RegionCapability{linodego.CapabilityVPCs})[0]
options.VPCType = linodego.Pointer(linodego.VPCTypeRDMA)
}}...)
require.NoError(t, err, "Error creating VPC with RDMA type")
defer vpcTeardown()
vpcCheck(vpc, t)
vpcCreateOptionsCheck(&createOpts, vpc, t)

vpc, err = client.GetVPC(context.Background(), vpc.ID)
require.NoError(t, err, "Error retrieving VPC")
vpcCreateOptionsCheck(&createOpts, vpc, t)

f := linodego.Filter{}
f.AddField(linodego.Eq, "vpc_type", "rdma")
filter, err := f.MarshalJSON()
require.NoError(t, err, "Error marshaling VPC list filter")

vpcs, err := client.ListVPCs(context.Background(), &linodego.ListOptions{Filter: string(filter)})
require.NoError(t, err, "Error listing VPCs")

var found *linodego.VPC
for idx := range vpcs {
if vpcs[idx].ID == vpc.ID {
found = &vpcs[idx]
break
}
}
require.NotNil(t, found, "VPC not found in list")
vpcCreateOptionsCheck(&createOpts, found, t)
}

func TestVPC_WithRDMATypeAndIPv6_Fail(t *testing.T) {
client, teardown := createTestClient(t, "fixtures/TestVPC_WithRDMATypeAndIPv6_Fail")
defer teardown()

createOpts := linodego.VPCCreateOptions{
Label: "go-test-vpc-" + getUniqueText(),
// GPUDirect RDMA capability not available for now
// Region: getRegionsWithCaps(t, client, []RegionCapability{linodego.CapabilityVPCs, linodego.CapabilityGPUDirectRDMA})[0]
Region: getRegionsWithCaps(t, client, []RegionCapability{linodego.CapabilityVPCs})[0],
VPCType: linodego.Pointer(linodego.VPCTypeRDMA),
IPv6: []VPCCreateOptionsIPv6{
{
Range: linodego.Pointer("/52"),
},
},
}
vpc, err := client.CreateVPC(context.Background(), createOpts)
require.Nil(t, vpc, "Expected VPC to be nil when creation fails")
require.Error(t, err, "Expected error creating VPC with RDMA type and IPv6")

var e *linodego.Error
require.ErrorAsf(t, err, &e, "Expected error to be of type *linodego.Error, got: %T", err)
assert.Equal(t, 400, e.Code, "Expected error code 400, got: %d", e.Code)
expectedErrorMessage := "RDMA VPCs cannot have IPv6 ranges"
assert.Contains(t, e.Message, expectedErrorMessage, "Expected error message to contain: %s, got: %s", expectedErrorMessage, e.Message)
}
25 changes: 25 additions & 0 deletions test/unit/fixtures/vpc_rdma_create.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"id": 39,
"label": "new-rdma-vpc",
"vpc_type": "rdma",
"description": "A new RDMA VPC",
"region": "fake-cph-5",
"ipv6": [],
"subnets": [
{
"id": 40,
"label": "rdma-subnet-1",
"ipv4": "10.0.0.0/24",
"vpc_type": "rdma",
"ipv6": [],
"linodes": [],
"databases": [],
"nodebalancers": [],
"created": "2026-03-12T09:30:01",
"updated": "2026-03-12T09:30:01"
}
],
"created": "2026-03-12T09:30:01",
"updated": "2026-03-12T09:30:01"
}

Loading