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
21 changes: 18 additions & 3 deletions test_suites/networkconfig/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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 {
Expand Down
140 changes: 140 additions & 0 deletions utils/networkutils/networkutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,18 @@ package networkutils
import (
"context"
"encoding/json"
"flag"
"fmt"
"math/big"
"regexp"
"sort"
"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 (
Expand All @@ -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 <NIC_TYPE>:<COUNT>. 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*(.*)$`)

Expand Down Expand Up @@ -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
}
172 changes: 172 additions & 0 deletions utils/networkutils/networkutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}
})
}
}