Files
nomad/client/allocrunner/networking_bridge_linux_test.go
Daniel Bennett 2f5cf8efae networking: option to enable ipv6 on bridge network (#23882)
by setting bridge_network_subnet_ipv6 in client config

Co-authored-by: Martina Santangelo <martina.santangelo@hashicorp.com>
2024-09-04 10:17:10 -05:00

199 lines
4.9 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package allocrunner
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/coreos/go-iptables/iptables"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/nomad/mock"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/shoenig/test/must"
)
func Test_buildNomadBridgeNetConfig(t *testing.T) {
ci.Parallel(t)
testCases := []struct {
name string
withConsulCNI bool
b *bridgeNetworkConfigurator
}{
{
name: "empty",
b: &bridgeNetworkConfigurator{},
},
{
name: "ipv6",
b: &bridgeNetworkConfigurator{
bridgeName: defaultNomadBridgeName,
allocSubnetIPv6: "3fff:cab0:0d13::/120",
allocSubnetIPv4: defaultNomadAllocSubnet,
},
},
{
name: "hairpin",
b: &bridgeNetworkConfigurator{
bridgeName: defaultNomadBridgeName,
allocSubnetIPv4: defaultNomadAllocSubnet,
hairpinMode: true,
},
},
{
name: "bad_input",
b: &bridgeNetworkConfigurator{
bridgeName: `bad"`,
allocSubnetIPv4: defaultNomadAllocSubnet,
hairpinMode: true,
},
},
{
name: "consul-cni",
withConsulCNI: true,
b: &bridgeNetworkConfigurator{
bridgeName: defaultNomadBridgeName,
allocSubnetIPv4: defaultNomadAllocSubnet,
hairpinMode: true,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
bCfg, err := buildNomadBridgeNetConfig(*tc.b, tc.withConsulCNI)
must.NoError(t, err)
// Validate that the JSON created is rational
must.True(t, json.Valid(bCfg))
// and that it matches golden expectations
goldenFile := filepath.Join("test_fixtures", tc.name+".conflist.json")
expect, err := os.ReadFile(goldenFile)
must.NoError(t, err)
must.Eq(t, string(expect), string(bCfg)+"\n")
})
}
}
func TestBridgeNetworkConfigurator_newIPTables_default(t *testing.T) {
t.Parallel()
b, err := newBridgeNetworkConfigurator(hclog.Default(),
mock.MinAlloc(),
"", "", "", "",
false, false,
mock.Node())
must.NoError(t, err)
for family, expect := range map[structs.NodeNetworkAF]iptables.Protocol{
"ipv6": iptables.ProtocolIPv6,
"ipv4": iptables.ProtocolIPv4,
"other": iptables.ProtocolIPv4,
} {
t.Run(string(family), func(t *testing.T) {
mgr, err := b.newIPTables(family)
must.NoError(t, err)
ipt := mgr.(*iptables.IPTables)
must.Eq(t, expect, ipt.Proto(), must.Sprint("unexpected ip family"))
})
}
}
func TestBridgeNetworkConfigurator_ensureForwardingRules(t *testing.T) {
t.Parallel()
newMockIPTables := func(b *bridgeNetworkConfigurator) (*mockIPTablesChain, *mockIPTablesChain) {
ipt := &mockIPTablesChain{}
ip6t := &mockIPTablesChain{}
b.newIPTables = func(fam structs.NodeNetworkAF) (IPTablesChain, error) {
switch fam {
case "ipv6":
return ip6t, nil
case "ipv4":
return ipt, nil
}
return nil, fmt.Errorf("unknown fam %q in newMockIPTables", fam)
}
return ipt, ip6t
}
cases := []struct {
name string
bridgeName, ip4, ip6 string
expectIP4Rules []string
expectIP6Rules []string
ip4Err, ip6Err error
}{
{
name: "defaults",
expectIP4Rules: []string{"-o", defaultNomadBridgeName, "-d", defaultNomadAllocSubnet, "-j", "ACCEPT"},
},
{
name: "configured",
bridgeName: "golden-gate",
ip4: "a.b.c.d/z",
ip6: "aa:bb:cc:dd/z",
expectIP4Rules: []string{"-o", "golden-gate", "-d", "a.b.c.d/z", "-j", "ACCEPT"},
expectIP6Rules: []string{"-o", "golden-gate", "-d", "aa:bb:cc:dd/z", "-j", "ACCEPT"},
},
{
name: "ip4error",
ip4Err: errors.New("test ip4error"),
},
{
name: "ip6error",
ip6: "aa:bb:cc:dd/z",
ip6Err: errors.New("test ip6error"),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
b, err := newBridgeNetworkConfigurator(hclog.Default(),
mock.MinAlloc(),
tc.bridgeName, tc.ip4, tc.ip6, "",
false, false,
mock.Node())
must.NoError(t, err)
ipt, ip6t := newMockIPTables(b)
ipt.newChainErr = tc.ip4Err
ip6t.newChainErr = tc.ip6Err
// method under test
err = b.ensureForwardingRules()
if tc.ip6Err != nil {
must.ErrorIs(t, err, tc.ip6Err)
return
}
if tc.ip4Err != nil {
must.ErrorIs(t, err, tc.ip4Err)
return
}
must.NoError(t, err)
must.Eq(t, ipt.chain, cniAdminChainName)
must.Eq(t, ipt.table, "filter")
must.Eq(t, ipt.rules, tc.expectIP4Rules)
if tc.expectIP6Rules != nil {
must.Eq(t, ip6t.chain, cniAdminChainName)
must.Eq(t, ip6t.table, "filter")
must.Eq(t, ip6t.rules, tc.expectIP6Rules)
} else {
must.Eq(t, "", ip6t.chain, must.Sprint("expect empty ip6tables chain"))
must.Eq(t, "", ip6t.table, must.Sprint("expect empty ip6tables table"))
must.Len(t, 0, ip6t.rules, must.Sprint("expect empty ip6tables rules"))
}
})
}
}