From e560f9db346f53d59a5712aba57b3f6f120bcd26 Mon Sep 17 00:00:00 2001 From: Aaron Nelson Date: Mon, 15 Jun 2026 14:45:23 -0700 Subject: [PATCH] Add networkutils specialized instance creation, use it in networkconfig Testing with `networkconfig` and the previous test harness (which was trying to use GVNIC, but wasn't really): > no config expectation found for machine type "n2-highcpu-32" and nic types [GVNIC] This is exactly what we want to see. We will add updated test expectations for GVNIC in a later change. PiperOrigin-RevId: 932677032 --- test_suites/networkconfig/setup.go | 21 ++- utils/networkutils/networkutils.go | 140 +++++++++++++++++++ utils/networkutils/networkutils_test.go | 172 ++++++++++++++++++++++++ 3 files changed, 330 insertions(+), 3 deletions(-) diff --git a/test_suites/networkconfig/setup.go b/test_suites/networkconfig/setup.go index 7090737..2edf2cf 100644 --- a/test_suites/networkconfig/setup.go +++ b/test_suites/networkconfig/setup.go @@ -19,7 +19,7 @@ import ( "fmt" "github.com/GoogleCloudPlatform/cloud-image-tests" - "github.com/GoogleCloudPlatform/compute-daisy" + "github.com/GoogleCloudPlatform/cloud-image-tests/utils/networkutils" "google.golang.org/api/compute/v1" ) @@ -38,13 +38,28 @@ func TestSetup(t *imagetest.TestWorkflow) error { } func createMachine(t *imagetest.TestWorkflow, machineType string, zone string) (*imagetest.TestVM, error) { + instanceName := "machine" + nicTypes, err := networkutils.ExpandNICTypes(*networkutils.NICTypesFlag) + if err != nil { + return nil, fmt.Errorf("expanding NIC types: %w", err) + } + disk := compute.Disk{ - Name: "machine", + Name: instanceName, Type: imagetest.DiskTypeNeeded(machineType), Zone: zone, } - instance := &daisy.Instance{} + instance, err := networkutils.CreateMachineWithNetworks(t, &networkutils.CreateMachineWithNetworksOptions{ + MachineName: instanceName, + MachineType: machineType, + NicTypes: nicTypes, + Project: t.Project.Name, + Zone: zone, + }) + if err != nil { + return nil, fmt.Errorf("creating machine with networks: %w", err) + } vm, err := t.CreateTestVMMultipleDisks([]*compute.Disk{&disk}, instance) if err != nil { diff --git a/utils/networkutils/networkutils.go b/utils/networkutils/networkutils.go index 816d2ed..d03b28e 100644 --- a/utils/networkutils/networkutils.go +++ b/utils/networkutils/networkutils.go @@ -18,6 +18,7 @@ package networkutils import ( "context" "encoding/json" + "flag" "fmt" "math/big" "regexp" @@ -25,7 +26,10 @@ import ( "strconv" "strings" + "github.com/GoogleCloudPlatform/cloud-image-tests" "github.com/GoogleCloudPlatform/cloud-image-tests/utils" + "github.com/GoogleCloudPlatform/compute-daisy" + "google.golang.org/api/compute/v1" ) const ( @@ -42,6 +46,9 @@ const ( ) var ( + // NICTypesFlag is the flag to specify the NIC types to use in the test. + NICTypesFlag = flag.String("networkutils_nic_types", "GVNIC:1", "NIC types. Comma separated list of :. e.g. \"GVNIC:2\" or \"GVNIC:2,MRDMA:8\". If unspecified, defaults to a single GVNIC.") + // EthtoolDriverRe is a regex to extract the driver name from the `ethtool -i` output. EthtoolDriverRe = regexp.MustCompile(`(?m)^driver:\s*(.*)$`) @@ -168,3 +175,136 @@ func ExpandNICTypes(condensedNicTypes string) ([]string, error) { return nicTypes, nil } + +func daisyNetworkForGeneralPurposeNIC(index int) *daisy.Network { + return &daisy.Network{ + Network: compute.Network{ + Name: fmt.Sprintf("network-%d", index), + Mtu: int64(imagetest.JumboFramesMTU), + }, + AutoCreateSubnetworks: new(false), + } +} + +func daisyNetworkForIRDMANIC(index int, project string, zone string, isMetal bool) *daisy.Network { + return &daisy.Network{ + Network: compute.Network{ + Name: fmt.Sprintf("irdma-network-%d", index), + Mtu: int64(imagetest.JumboFramesMTU), + NetworkProfile: fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/networkProfiles/%s-vpc-falcon", project, zone), + }, + AutoCreateSubnetworks: new(false), + } +} + +func daisyNetworkForMRDMANIC(index int, project string, zone string, isMetal bool) *daisy.Network { + networkProfile := fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/networkProfiles/%s-vpc-roce", project, zone) + if isMetal { + networkProfile += "-metal" + } + return &daisy.Network{ + Network: compute.Network{ + Name: fmt.Sprintf("mrdma-network-%d", index), + Mtu: int64(imagetest.JumboFramesMTU), + NetworkProfile: networkProfile, + }, + AutoCreateSubnetworks: new(false), + } +} + +func daisyNetworkForNIC(nicType string, index int, project string, zone string, isMetal bool) (*daisy.Network, error) { + switch nicType { + case NICTypeVIRTIONET: + return daisyNetworkForGeneralPurposeNIC(index), nil + case NICTypeGVNIC: + return daisyNetworkForGeneralPurposeNIC(index), nil + case NICTypeIDPF: + return daisyNetworkForGeneralPurposeNIC(index), nil + case NICTypeIRDMA: + return daisyNetworkForIRDMANIC(index, project, zone, isMetal), nil + case NICTypeMRDMA: + return daisyNetworkForMRDMANIC(index, project, zone, isMetal), nil + default: + return nil, fmt.Errorf("unsupported NIC type: %q", nicType) + } +} + +func subnetPrefix(index int) (string, error) { + if index < 0 || index > 255 { + return "", fmt.Errorf("index out of range [0, 255] is not supported, got %d", index) + } + return fmt.Sprintf("10.0.%d.0/24", index), nil +} + +func regionFromZone(zone string) (string, error) { + parts := strings.Split(zone, "-") + if len(parts) < 2 { + return "", fmt.Errorf("failed to parse region from zone %q", zone) + } + return strings.Join(parts[:2], "-"), nil +} + +func daisySubnet(index int, zone string) (*daisy.Subnetwork, error) { + netPrefix, err := subnetPrefix(index) + if err != nil { + return nil, err + } + + region, err := regionFromZone(zone) + if err != nil { + return nil, err + } + + return &daisy.Subnetwork{ + Subnetwork: compute.Subnetwork{ + Name: fmt.Sprintf("subnet-%d", index), + IpCidrRange: netPrefix, + Region: region, + }, + }, nil +} + +// CreateMachineWithNetworksOptions contains the options for creating a machine with multiple +// network interfaces. +type CreateMachineWithNetworksOptions struct { + MachineName string + MachineType string + NicTypes []string + Project string + Zone string +} + +// CreateMachineWithNetworks creates a daisy instance with the given network interfaces. +// It registers the networks and subnetwork creations with the test workflow. +func CreateMachineWithNetworks(t *imagetest.TestWorkflow, o *CreateMachineWithNetworksOptions) (*daisy.Instance, error) { + m := &daisy.Instance{} + + for nicIndex, nicType := range o.NicTypes { + daisyNetwork, err := daisyNetworkForNIC(nicType, nicIndex, o.Project, o.Zone, imagetest.IsMetal(o.MachineType)) + if err != nil { + return nil, fmt.Errorf("building daisy network: %w", err) + } + + daisySubnet, err := daisySubnet(nicIndex, o.Zone) + if err != nil { + return nil, fmt.Errorf("building daisy subnetwork: %w", err) + } + + citNetwork, err := t.CreateNetworkFromDaisyNetwork(daisyNetwork) + if err != nil { + return nil, fmt.Errorf("creating network: %w", err) + } + + if _, err = citNetwork.CreateSubnetworkFromDaisySubnetwork(daisySubnet); err != nil { + return nil, fmt.Errorf("creating subnetwork: %w", err) + } + + m.NetworkInterfaces = append(m.NetworkInterfaces, &compute.NetworkInterface{ + NicType: nicType, + Network: daisyNetwork.Name, + Subnetwork: daisySubnet.Name, + }) + } + + return m, nil +} diff --git a/utils/networkutils/networkutils_test.go b/utils/networkutils/networkutils_test.go index 4edbfe9..883ed09 100644 --- a/utils/networkutils/networkutils_test.go +++ b/utils/networkutils/networkutils_test.go @@ -21,7 +21,10 @@ import ( "net/url" "testing" + "github.com/GoogleCloudPlatform/compute-daisy" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "google.golang.org/api/compute/v1" ) // makeRange returns a slice of integers from low to high, inclusive. @@ -324,3 +327,172 @@ func TestExpandNICTypes(t *testing.T) { }) } } + +func diffDaisy(a any, b any) string { + return cmp.Diff(a, b, cmpopts.IgnoreUnexported(daisy.Resource{})) +} + +func TestDaisyNetworkForNIC(t *testing.T) { + cases := []struct { + name string + nicType string + index int + project string + zone string + isMetal bool + want *daisy.Network + }{ + { + name: "VIRTIO NIC", + nicType: NICTypeVIRTIONET, + index: 0, + want: &daisy.Network{ + Network: compute.Network{ + Name: "network-0", + Mtu: 8896, + }, + AutoCreateSubnetworks: new(false), + }, + }, + { + name: "GVNIC NIC", + nicType: NICTypeGVNIC, + index: 0, + want: &daisy.Network{ + Network: compute.Network{ + Name: "network-0", + Mtu: 8896, + }, + AutoCreateSubnetworks: new(false), + }, + }, + { + name: "IDPF NIC", + nicType: NICTypeIDPF, + index: 0, + want: &daisy.Network{ + Network: compute.Network{ + Name: "network-0", + Mtu: 8896, + }, + AutoCreateSubnetworks: new(false), + }, + }, + { + name: "IRDMA NIC", + nicType: NICTypeIRDMA, + index: 0, + project: "test-project", + zone: "us-central1-a", + want: &daisy.Network{ + Network: compute.Network{ + Name: "irdma-network-0", + Mtu: 8896, + NetworkProfile: "https://www.googleapis.com/compute/v1/projects/test-project/global/networkProfiles/us-central1-a-vpc-falcon", + }, + AutoCreateSubnetworks: new(false), + }, + }, + { + name: "MRDMA virtual NIC", + nicType: NICTypeMRDMA, + index: 0, + project: "test-project", + zone: "europe-central2-a", + want: &daisy.Network{ + Network: compute.Network{ + Name: "mrdma-network-0", + Mtu: 8896, + NetworkProfile: "https://www.googleapis.com/compute/v1/projects/test-project/global/networkProfiles/europe-central2-a-vpc-roce", + }, + AutoCreateSubnetworks: new(false), + }, + }, + { + name: "MRDMA metal NIC", + nicType: NICTypeMRDMA, + index: 0, + project: "test-project", + zone: "asia-southeast2-b", + isMetal: true, + want: &daisy.Network{ + Network: compute.Network{ + Name: "mrdma-network-0", + Mtu: 8896, + NetworkProfile: "https://www.googleapis.com/compute/v1/projects/test-project/global/networkProfiles/asia-southeast2-b-vpc-roce-metal", + }, + AutoCreateSubnetworks: new(false), + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := daisyNetworkForNIC(tc.nicType, tc.index, tc.project, tc.zone, tc.isMetal) + if err != nil { + t.Fatalf("daisyNetworkForNIC(%q, %d, %q, %q, %t) failed: %v", tc.nicType, tc.index, tc.project, tc.zone, tc.isMetal, err) + } + if diff := diffDaisy(got, tc.want); diff != "" { + t.Errorf("daisyNetworkForNIC(%q, %d, %q, %q, %t) = doesn't match expectations: diff (-got +want):\n%s", tc.nicType, tc.index, tc.project, tc.zone, tc.isMetal, diff) + } + }) + } +} + +func TestDaisySubnet(t *testing.T) { + cases := []struct { + name string + index int + zone string + want *daisy.Subnetwork + wantErr bool + }{ + { + name: "index 0", + index: 0, + zone: "us-central1-a", + want: &daisy.Subnetwork{ + Subnetwork: compute.Subnetwork{ + Name: "subnet-0", + IpCidrRange: "10.0.0.0/24", + Region: "us-central1", + }, + }, + }, + { + name: "index 1", + index: 1, + zone: "europe-west1-b", + want: &daisy.Subnetwork{ + Subnetwork: compute.Subnetwork{ + Name: "subnet-1", + IpCidrRange: "10.0.1.0/24", + Region: "europe-west1", + }, + }, + }, + { + name: "index too low", + index: -1, + wantErr: true, + }, + { + name: "index too high", + index: 256, + wantErr: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := daisySubnet(tc.index, tc.zone) + if (err != nil) != tc.wantErr { + t.Errorf("daisySubnet(%d, %q) returned error %v, wantErr %t", tc.index, tc.zone, err, tc.wantErr) + return + } + if diff := diffDaisy(got, tc.want); diff != "" { + t.Errorf("daisySubnet(%d, %q) = doesn't match expectations: diff (-got +want):\n%s", tc.index, tc.zone, diff) + } + }) + } +}