Generalize create network code in network package integration tests

This commit is contained in:
Brian Picciano 2024-10-06 19:38:35 +02:00
parent 010c53e5c7
commit f146b77187
3 changed files with 242 additions and 132 deletions

View File

@ -113,16 +113,22 @@ func nebulaConfig(
} else { } else {
_, port, err := net.SplitHostPort(publicAddr) host, port, err := net.SplitHostPort(publicAddr)
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"parsing public address %q: %w", publicAddr, err, "parsing public address %q: %w", publicAddr, err,
) )
} }
// This helps with integration testing, so we can set a test to listen
// on some local IP without conflicting with something else running on
// the host.
if hostIP := net.ParseIP(host); hostIP == nil || !hostIP.IsLoopback() {
host = "0.0.0.0"
}
config["listen"] = map[string]string{ config["listen"] = map[string]string{
"host": "0.0.0.0", "host": host,
"port": port, "port": port,
} }

View File

@ -1,152 +1,29 @@
package network package network
import ( import (
"context"
"fmt"
"os"
"path/filepath"
"sync"
"sync/atomic"
"testing" "testing"
"isle/bootstrap"
"isle/daemon/daecommon"
"isle/nebula"
"isle/toolkit" "isle/toolkit"
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
"gopkg.in/yaml.v3"
) )
var ( // TODO seeing more of these logs than I'd expect:
getEnvBinDirPath = sync.OnceValue(func() string { // INFO [network/children] Creating UDP socket from nebula addr "lUDPAddr"="172.16.1.1:0" "rUDPAddr"="172.16.1.1:45535"
appDirPath := os.Getenv("APPDIR")
if appDirPath == "" {
panic("APPDIR not set")
}
return filepath.Join(appDirPath, "bin")
})
ipNetCounter uint64
)
func newIPNet(t *testing.T) nebula.IPNet {
var (
ipNet nebula.IPNet
ipNetStr = fmt.Sprintf(
"172.16.%d.0/24", atomic.AddUint64(&ipNetCounter, 1),
)
)
if err := ipNet.UnmarshalText([]byte(ipNetStr)); err != nil {
t.Fatalf("parsing IPNet from %q: %v", ipNetStr, err)
}
return ipNet
}
func mustParseNetworkConfigf(str string, args ...any) daecommon.NetworkConfig {
str = fmt.Sprintf(str, args...)
var networkConfig daecommon.NetworkConfig
if err := yaml.Unmarshal([]byte(str), &networkConfig); err != nil {
panic(fmt.Sprintf("parsing network config: %v", err))
}
return networkConfig
}
type harness struct {
ctx context.Context
logger *mlog.Logger
rootDir toolkit.Dir
dirCounter uint64
}
func newHarness(t *testing.T) *harness {
return &harness{
ctx: context.Background(),
logger: mlog.NewLogger(nil),
rootDir: toolkit.Dir{Path: t.TempDir()},
}
}
func (h *harness) mkDir(t *testing.T, name string) toolkit.Dir {
fullName := fmt.Sprintf("%s-%d", name, atomic.AddUint64(&h.dirCounter, 1))
t.Logf("Creating directory %q", fullName)
d, err := h.rootDir.MkChildDir(fullName, false)
if err != nil {
t.Fatalf("creating %q: %v", fullName, err)
}
return d
}
func TestCreate(t *testing.T) { func TestCreate(t *testing.T) {
toolkit.MarkIntegrationTest(t) toolkit.MarkIntegrationTest(t)
var ( var (
h = newHarness(t) h = newIntegrationHarness(t)
creationParams = bootstrap.NewCreationParams("test", "test.localnet") network = h.createNetwork(t, "primus", nil)
networkConfig = mustParseNetworkConfigf(`
vpn:
public_addr: "127.0.0.1:10000"
tun:
device: isle-test
storage:
allocations:
- data_path: %s
meta_path: %s
capacity: 1
- data_path: %s
meta_path: %s
capacity: 1
- data_path: %s
meta_path: %s
capacity: 1
`,
h.mkDir(t, "data").Path,
h.mkDir(t, "meta").Path,
h.mkDir(t, "data").Path,
h.mkDir(t, "meta").Path,
h.mkDir(t, "data").Path,
h.mkDir(t, "meta").Path,
) )
stateDir = h.mkDir(t, "state")
runtimeDir = h.mkDir(t, "runtime")
ipNet = newIPNet(t)
hostName = nebula.HostName("primus")
)
network, err := Create(
h.ctx,
h.logger.WithNamespace("network"),
networkConfig,
getEnvBinDirPath(),
stateDir,
runtimeDir,
creationParams,
ipNet,
hostName,
nil,
)
if err != nil {
t.Fatalf("creating Network: %v", err)
}
t.Cleanup(func() {
t.Log("Shutting down Network")
if err := network.Shutdown(); err != nil {
t.Logf("Shutting down Network failed: %v", err)
}
})
gotCreationParams, err := network.GetNetworkCreationParams(h.ctx) gotCreationParams, err := network.GetNetworkCreationParams(h.ctx)
if err != nil { if err != nil {
t.Fatalf("calling GetNetworkCreationParams: %v", err) t.Fatalf("calling GetNetworkCreationParams: %v", err)
} else if creationParams != gotCreationParams { } else if network.creationParams != gotCreationParams {
t.Fatalf( t.Fatalf(
"expected CreationParams %+v, got %+v", "expected CreationParams %+v, got %+v",
creationParams, network.creationParams,
gotCreationParams, gotCreationParams,
) )
} }

View File

@ -0,0 +1,227 @@
package network
import (
"context"
"fmt"
"isle/bootstrap"
"isle/daemon/children"
"isle/daemon/daecommon"
"isle/nebula"
"isle/toolkit"
"os"
"path/filepath"
"sync"
"sync/atomic"
"testing"
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
"gopkg.in/yaml.v3"
)
// Utilities related to running network integration tests
var (
getEnvBinDirPath = sync.OnceValue(func() string {
appDirPath := os.Getenv("APPDIR")
if appDirPath == "" {
panic("APPDIR not set")
}
return filepath.Join(appDirPath, "bin")
})
ipNetCounter uint64 = 0
publicAddrPortCounter uint64 = 1024
tunDeviceCounter uint64 = 0
)
func newIPNet() nebula.IPNet {
var (
ipNet nebula.IPNet
ipNetStr = fmt.Sprintf(
"172.16.%d.0/24", atomic.AddUint64(&ipNetCounter, 1),
)
)
if err := ipNet.UnmarshalText([]byte(ipNetStr)); err != nil {
panic(fmt.Sprintf("parsing IPNet from %q: %v", ipNetStr, err))
}
return ipNet
}
func newPublicAddr() string {
return fmt.Sprintf(
"127.0.0.200:%d", atomic.AddUint64(&publicAddrPortCounter, 1),
)
}
func newTunDevice() string {
return fmt.Sprintf("isle-test-%d", atomic.AddUint64(&tunDeviceCounter, 1))
}
func mustParseNetworkConfigf(str string, args ...any) daecommon.NetworkConfig {
str = fmt.Sprintf(str, args...)
var networkConfig daecommon.NetworkConfig
if err := yaml.Unmarshal([]byte(str), &networkConfig); err != nil {
panic(fmt.Sprintf("parsing network config: %v", err))
}
return networkConfig
}
type integrationHarness struct {
ctx context.Context
logger *mlog.Logger
rootDir toolkit.Dir
dirCounter uint64
}
func newIntegrationHarness(t *testing.T) *integrationHarness {
toolkit.MarkIntegrationTest(t)
rootDir, err := os.MkdirTemp("", "isle-network-it-test.*")
if err != nil {
t.Fatalf("creating root temp dir: %v", err)
}
t.Cleanup(func() {
if t.Failed() {
t.Logf("Temp directory for failed test not deleted: %q", rootDir)
return
}
t.Logf("Deleting temp directory %q", rootDir)
if err := os.RemoveAll(rootDir); err != nil {
t.Errorf("failed to remove %q: %v", rootDir, err)
}
})
return &integrationHarness{
ctx: context.Background(),
logger: mlog.NewLogger(nil),
rootDir: toolkit.Dir{Path: rootDir},
}
}
func (h *integrationHarness) mkDir(t *testing.T, name string) toolkit.Dir {
fullName := fmt.Sprintf("%s-%d", name, atomic.AddUint64(&h.dirCounter, 1))
t.Logf("Creating directory %q", fullName)
d, err := h.rootDir.MkChildDir(fullName, false)
if err != nil {
t.Fatalf("creating %q: %v", fullName, err)
}
return d
}
type createNetworkOpts struct {
creationParams bootstrap.CreationParams
noCleanup bool
}
func (o *createNetworkOpts) withDefaults() *createNetworkOpts {
if o == nil {
o = new(createNetworkOpts)
}
if o.creationParams == (bootstrap.CreationParams{}) {
o.creationParams = bootstrap.NewCreationParams("test", "test.localnet")
}
return o
}
type integrationHarnessNetwork struct {
Network
creationParams bootstrap.CreationParams
stateDir, runtimeDir toolkit.Dir
}
func (h *integrationHarness) createNetwork(
t *testing.T,
hostNameStr string,
opts *createNetworkOpts,
) integrationHarnessNetwork {
opts = opts.withDefaults()
var (
networkConfig = mustParseNetworkConfigf(`
vpn:
public_addr: %q
tun:
device: %q
storage:
allocations:
- data_path: %s
meta_path: %s
capacity: 1
- data_path: %s
meta_path: %s
capacity: 1
- data_path: %s
meta_path: %s
capacity: 1
`,
newPublicAddr(),
newTunDevice(),
h.mkDir(t, "data").Path,
h.mkDir(t, "meta").Path,
h.mkDir(t, "data").Path,
h.mkDir(t, "meta").Path,
h.mkDir(t, "data").Path,
h.mkDir(t, "meta").Path,
)
stateDir = h.mkDir(t, "state")
runtimeDir = h.mkDir(t, "runtime")
childrenLogFilePath = filepath.Join(runtimeDir.Path, "children.log")
ipNet = newIPNet()
hostName = nebula.HostName(hostNameStr)
)
childrenLogFile, err := os.Create(childrenLogFilePath)
if err != nil {
t.Fatalf("creating %q: %v", childrenLogFilePath, err)
}
t.Cleanup(func() {
if err := childrenLogFile.Close(); err != nil {
t.Errorf("closing %q: %v", childrenLogFilePath, err)
}
})
network, err := Create(
h.ctx,
h.logger.WithNamespace("network"),
networkConfig,
getEnvBinDirPath(),
stateDir,
runtimeDir,
opts.creationParams,
ipNet,
hostName,
&Opts{
ChildrenOpts: &children.Opts{
Stdout: childrenLogFile,
Stderr: childrenLogFile,
},
},
)
if err != nil {
t.Fatalf("creating Network: %v", err)
}
if !opts.noCleanup {
t.Cleanup(func() {
t.Log("Shutting down Network")
if err := network.Shutdown(); err != nil {
t.Logf("Shutting down Network failed: %v", err)
}
})
}
return integrationHarnessNetwork{
network, opts.creationParams, stateDir, runtimeDir,
}
}