package network import ( "context" "fmt" "io" "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 { t.Parallel() toolkit.MarkIntegrationTest(t) rootDir, err := os.MkdirTemp("", "isle-network-it-test.*") if err != nil { t.Fatalf("creating root temp dir: %v", err) } t.Logf("Temporary test directory: %q", rootDir) 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 manualShutdown 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 networkConfig daecommon.NetworkConfig stateDir, runtimeDir toolkit.Dir opts *Opts } 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) childrenOpts *children.Opts ) 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) } }) if os.Getenv("ISLE_INTEGRATION_TEST_CHILDREN_LOG_STDOUT") == "" { childrenOpts = &children.Opts{ Stdout: childrenLogFile, Stderr: childrenLogFile, } } else { childrenOpts = &children.Opts{ Stdout: io.MultiWriter(os.Stdout, childrenLogFile), Stderr: io.MultiWriter(os.Stdout, childrenLogFile), } } networkOpts := &Opts{ ChildrenOpts: childrenOpts, } network, err := Create( h.ctx, h.logger.WithNamespace("network"), networkConfig, getEnvBinDirPath(), stateDir, runtimeDir, opts.creationParams, ipNet, hostName, networkOpts, ) if err != nil { t.Fatalf("creating Network: %v", err) } if !opts.manualShutdown { 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, networkConfig, stateDir, runtimeDir, networkOpts, } }