refactor: Use koanf.Koanf in e2e tests to pass agent configuration instead of CLI arguments

Signed-off-by: Steffen Vogel <post@steffenvogel.de>
This commit is contained in:
Steffen Vogel
2023-08-10 07:55:45 +02:00
parent a4a9d14765
commit ff10036140
18 changed files with 244 additions and 161 deletions

View File

@@ -10,7 +10,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/pion/ice/v2"
"github.com/pion/stun" "github.com/pion/stun"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
@@ -33,8 +32,19 @@ var _ = Describe("Agent config", func() {
}) })
DescribeTable("can parse ICE urls with credentials", DescribeTable("can parse ICE urls with credentials",
func(args []string, exp any) { func(url, username, password string, exp any) {
cfg, err := config.ParseArgs(args...) iceCfgStr := fmt.Sprintf("urls: [ '%s' ]", url)
if username != "" {
iceCfgStr += fmt.Sprintf(", username: '%s'", username)
}
if password != "" {
iceCfgStr += fmt.Sprintf(", password: '%s'", password)
}
cfgStr := fmt.Sprintf("ice: { %s }", iceCfgStr)
cfg, err := config.ParseRaw(cfgStr)
Expect(err).To(Succeed()) Expect(err).To(Succeed())
icfg := cfg.DefaultInterfaceSettings icfg := cfg.DefaultInterfaceSettings
@@ -50,7 +60,7 @@ var _ = Describe("Agent config", func() {
Expect(aCfg.Urls).To(ContainElements(exp)) Expect(aCfg.Urls).To(ContainElements(exp))
} }
}, },
Entry("url1", []string{"--ice-url", "stun:server1", "--ice-username", "user1", "--ice-password", "pass1"}, &stun.URI{ Entry("url1", "stun:server1", "user1", "pass1", &stun.URI{
Scheme: stun.SchemeTypeSTUN, Scheme: stun.SchemeTypeSTUN,
Host: "server1", Host: "server1",
Port: 3478, Port: 3478,
@@ -58,7 +68,7 @@ var _ = Describe("Agent config", func() {
Username: "user1", Username: "user1",
Password: "pass1", Password: "pass1",
}), }),
Entry("url2", []string{"--ice-url", "turn:server2:1234?transport=tcp", "--ice-username", "user1", "--ice-password", "pass1"}, &stun.URI{ Entry("url2", "turn:server2:1234?transport=tcp", "user1", "pass1", &stun.URI{
Scheme: stun.SchemeTypeTURN, Scheme: stun.SchemeTypeTURN,
Host: "server2", Host: "server2",
Port: 1234, Port: 1234,
@@ -66,7 +76,7 @@ var _ = Describe("Agent config", func() {
Username: "user1", Username: "user1",
Password: "pass1", Password: "pass1",
}), }),
Entry("url3", []string{"--ice-url", "turn:user3:pass3@server3:1234?transport=tcp", "--ice-password", "pass3"}, &stun.URI{ Entry("url3", "turn:user3:pass3@server3:1234?transport=tcp", "", "pass3", &stun.URI{
Scheme: stun.SchemeTypeTURN, Scheme: stun.SchemeTypeTURN,
Host: "server3", Host: "server3",
Port: 1234, Port: 1234,
@@ -74,8 +84,8 @@ var _ = Describe("Agent config", func() {
Username: "user3", Username: "user3",
Password: "pass3", Password: "pass3",
}), }),
Entry("url3", []string{"--ice-password", "pass1", "--ice-url", "http://bla.0l.de"}, "failed to gather ICE URLs: invalid ICE URL scheme: http"), Entry("url3", "http://bla.0l.de", "", "pass1", "failed to gather ICE URLs: invalid ICE URL scheme: http"),
Entry("url4", []string{"--ice-url", "stun:stun.cunicu.li?transport=tcp"}, "failed to gather ICE URLs: failed to parse STUN/TURN URL 'stun:stun.cunicu.li?transport=tcp': queries not supported in stun address"), Entry("url4", "stun:stun.cunicu.li?transport=tcp", "", "", "failed to gather ICE URLs: failed to parse STUN/TURN URL 'stun:stun.cunicu.li?transport=tcp': queries not supported in stun address"),
) )
Context("can get ICE URLs from relay API", func() { Context("can get ICE URLs from relay API", func() {
@@ -120,7 +130,8 @@ var _ = Describe("Agent config", func() {
}) })
It("can get list of relays", func() { It("can get list of relays", func() {
cfg, err := config.ParseArgs("--ice-url", fmt.Sprintf("grpc://localhost:%d?insecure=true", port)) cfgStr := fmt.Sprintf("ice: { urls: [ 'grpc://localhost:%d?insecure=true' ] }", port)
cfg, err := config.ParseRaw(cfgStr)
Expect(err).To(Succeed()) Expect(err).To(Succeed())
ctx := context.Background() ctx := context.Background()
@@ -160,20 +171,6 @@ var _ = Describe("Agent config", func() {
}) })
}) })
It("can parse multiple candidate types", func() {
cfg, err := config.ParseArgs(
"--ice-candidate-type", "host",
"--ice-candidate-type", "relay",
)
Expect(err).To(Succeed())
icfg := cfg.DefaultInterfaceSettings
aCfg, err := icfg.AgentConfig(context.Background(), &pk)
Expect(err).To(Succeed())
Expect(aCfg.CandidateTypes).To(ConsistOf(ice.CandidateTypeRelay, ice.CandidateTypeHost))
})
It("can parse multiple backend URLs when passed as individual command line arguments", func() { It("can parse multiple backend URLs when passed as individual command line arguments", func() {
cfg, err := config.ParseArgs( cfg, err := config.ParseArgs(
"--backend", "grpc://server1", "--backend", "grpc://server1",
@@ -182,6 +179,8 @@ var _ = Describe("Agent config", func() {
Expect(err).To(Succeed()) Expect(err).To(Succeed())
Expect(cfg.Backends).To(HaveLen(2)) Expect(cfg.Backends).To(HaveLen(2))
Expect(cfg.Backends[0].Host).To(Equal("server1"))
Expect(cfg.Backends[1].Host).To(Equal("server2"))
}) })
It("can parse multiple backend URLs when passed as comma-separated command line arguments", func() { It("can parse multiple backend URLs when passed as comma-separated command line arguments", func() {

View File

@@ -14,6 +14,7 @@ import (
"dario.cat/mergo" "dario.cat/mergo"
"github.com/knadh/koanf/parsers/yaml" "github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/confmap" "github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/providers/rawbytes"
"github.com/knadh/koanf/v2" "github.com/knadh/koanf/v2"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"github.com/spf13/pflag" "github.com/spf13/pflag"
@@ -25,6 +26,8 @@ type Config struct {
*Settings *Settings
*Meta *Meta
*koanf.Koanf *koanf.Koanf
Runtime *koanf.Koanf
Sources []*Source
ExtraSources []*Source ExtraSources []*Source
// Settings which are not configurable via configuration file // Settings which are not configurable via configuration file
@@ -55,6 +58,16 @@ func ParseArgs(args ...string) (*Config, error) {
return c, c.Init(c.flags.Args()) return c, c.Init(c.flags.Args())
} }
func ParseRaw(cfg string) (*Config, error) {
c := New(nil)
c.ExtraSources = append(c.ExtraSources, &Source{
Provider: rawbytes.Provider([]byte(cfg)),
})
return c, c.Init(nil)
}
// New creates a new configuration instance. // New creates a new configuration instance.
func New(flags *pflag.FlagSet) *Config { func New(flags *pflag.FlagSet) *Config {
if flags == nil { if flags == nil {
@@ -140,6 +153,8 @@ func (c *Config) Init(args []string) error {
} }
} }
c.Sources = append(c.Sources, c.ExtraSources...)
_, err = c.Reload() _, err = c.Reload()
return err return err

View File

@@ -8,6 +8,7 @@ import (
"strings" "strings"
"github.com/knadh/koanf/parsers/yaml" "github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/rawbytes"
"github.com/knadh/koanf/v2" "github.com/knadh/koanf/v2"
) )
@@ -44,7 +45,7 @@ func (s *Source) Load() error {
func load(p koanf.Provider) (*koanf.Koanf, []string, error) { func load(p koanf.Provider) (*koanf.Koanf, []string, error) {
var q koanf.Parser var q koanf.Parser
switch p.(type) { switch p.(type) {
case *RemoteFileProvider, *LocalFileProvider: case *RemoteFileProvider, *LocalFileProvider, *rawbytes.RawBytes:
q = yaml.Parser() q = yaml.Parser()
default: default:
q = nil q = nil

View File

@@ -94,7 +94,7 @@ var _ = Context("nat double: Carrier Grade NAT setup with two relays and a singl
// Mixed IPv4/IPv6 NAT tests are currently broken for some reason. // Mixed IPv4/IPv6 NAT tests are currently broken for some reason.
// Hence we limit ourself to IPv6 here. // Hence we limit ourself to IPv6 here.
// See: https://github.com/stv0g/cunicu/issues/224 // See: https://github.com/stv0g/cunicu/issues/224
opt.ExtraArgs{"--ice-network-type", "udp6,tcp6"}, opt.ConfigValue("ice.network_types", []string{"udp6", "tcp6"}),
) )
switch { switch {

View File

@@ -78,8 +78,8 @@ var _ = Context("nat simple: Simple home-router NAT setup", func() {
// Mixed IPv4/IPv6 NAT tests are currently broken for some reason. // Mixed IPv4/IPv6 NAT tests are currently broken for some reason.
// Hence we limit ourself to IPv6 here. // Hence we limit ourself to IPv6 here.
// See: https://github.com/stv0g/cunicu/issues/224 // See: https://github.com/stv0g/cunicu/issues/224
opt.ExtraArgs{"--ice-network-type", "udp6,tcp6"}, opt.ConfigValue("ice.network_types", []string{"udp6", "tcp6"}),
// opt.ExtraArgs{"--ice-candidate-type", "host,srflx"}, // opt.ICECandidateTypes(ice.CandidateTypeHost, ice.CandidateTypeServerReflexive),
) )
Expect(err).To(Succeed(), "Failed to created nodes: %s", err) Expect(err).To(Succeed(), "Failed to created nodes: %s", err)

View File

@@ -31,57 +31,67 @@ func (n *Network) ConnectivityTests() {
}) })
} }
func (n *Network) ConnectivityTestsWithExtraArgs(extraArgs ...any) {
BeforeEach(func() {
n.AgentOptions = append(n.AgentOptions,
opt.ExtraArgs(extraArgs),
)
})
n.ConnectivityTests()
}
func (n *Network) ConnectivityTestsForAllCandidateTypes() { func (n *Network) ConnectivityTestsForAllCandidateTypes() {
ConnectivityTests := func() {
Context("ipv4: Allow IPv4 network only", func() {
BeforeEach(func() {
n.AgentOptions = append(n.AgentOptions, opt.ConfigValue("ice.network_types", "udp4"))
})
n.ConnectivityTests()
})
Context("ipv6: Allow IPv6 network only", func() {
BeforeEach(func() {
n.AgentOptions = append(n.AgentOptions, opt.ConfigValue("ice.network_types", "udp6"))
})
n.ConnectivityTests()
})
}
Context("candidate-types", func() { Context("candidate-types", func() {
Context("any: Allow any candidate type", func() { Context("any: Allow any candidate type", func() {
Context("ipv4: Allow IPv4 network only", func() { ConnectivityTests()
n.ConnectivityTestsWithExtraArgs("--ice-network-type", "udp4")
})
Context("ipv6: Allow IPv6 network only", func() {
n.ConnectivityTestsWithExtraArgs("--ice-network-type", "udp6")
})
}) })
Context("host: Allow only host candidates", func() { Context("host: Allow only host candidates", func() {
Context("ipv4: Allow IPv4 network only", func() { BeforeEach(func() {
n.ConnectivityTestsWithExtraArgs("--ice-candidate-type", "host", "--ice-network-type", "udp4") // , "--port-forwarding=false") n.AgentOptions = append(n.AgentOptions, opt.ConfigValue("ice.candidate_types", "host"))
}) })
Context("ipv6: Allow IPv6 network only", func() { ConnectivityTests()
n.ConnectivityTestsWithExtraArgs("--ice-candidate-type", "host", "--ice-network-type", "udp6")
})
}) })
Context("srflx: Allow only server reflexive candidates", func() { Context("srflx: Allow only server reflexive candidates", func() {
Context("ipv4: Allow IPv4 network only", func() { BeforeEach(func() {
n.ConnectivityTestsWithExtraArgs("--ice-candidate-type", "srflx", "--ice-network-type", "udp4") n.AgentOptions = append(n.AgentOptions, opt.ConfigValue("ice.candidate_types", "srflx"))
}) })
Context("ipv6: Allow IPv6 network only", func() { ConnectivityTests()
n.ConnectivityTestsWithExtraArgs("--ice-candidate-type", "srflx", "--ice-network-type", "udp6")
})
}) })
Context("relay: Allow only relay candidates", func() { Context("relay: Allow only relay candidates", func() {
BeforeEach(func() {
n.AgentOptions = append(n.AgentOptions, opt.ConfigValue("ice.candidate_types", "relay"))
})
Context("ipv4: Allow IPv4 network only", func() { Context("ipv4: Allow IPv4 network only", func() {
n.ConnectivityTestsWithExtraArgs("--ice-candidate-type", "relay", "--ice-network-type", "udp4") BeforeEach(func() {
n.AgentOptions = append(n.AgentOptions, opt.ConfigValue("ice.network_types", "udp4"))
})
n.ConnectivityTests()
}) })
// TODO: Check why IPv6 relay is not working // TODO: Check why IPv6 relay is not working
// Blocked by: https://github.com/pion/ice/pull/462 // Blocked by: https://github.com/pion/ice/pull/462
Context("ipv6", Pending, func() { Context("ipv6: Allow IPv6 network only", Pending, func() {
n.ConnectivityTestsWithExtraArgs("--ice-candidate-type", "relay", "--ice-network-type", "udp6") BeforeEach(func() {
n.AgentOptions = append(n.AgentOptions, opt.ConfigValue("ice.network_types", "udp6"))
})
n.ConnectivityTests()
}) })
}) })
}) })

View File

@@ -9,6 +9,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/knadh/koanf/v2"
opt "github.com/stv0g/cunicu/test/e2e/nodes/options"
g "github.com/stv0g/gont/v2/pkg" g "github.com/stv0g/gont/v2/pkg"
gopt "github.com/stv0g/gont/v2/pkg/options" gopt "github.com/stv0g/gont/v2/pkg/options"
copt "github.com/stv0g/gont/v2/pkg/options/capture" copt "github.com/stv0g/gont/v2/pkg/options/capture"
@@ -108,32 +110,28 @@ func (n *Network) Start() {
By("Starting agent nodes") By("Starting agent nodes")
err = n.AgentNodes.Start(n.BasePath, n.AgentArgs()...) err = n.AgentNodes.Start(n.BasePath, n.AgentConfig())
Expect(err).To(Succeed(), "Failed to start cunicu: %s", err) Expect(err).To(Succeed(), "Failed to start cunicu: %s", err)
} }
func (n *Network) AgentArgs() []any { func (n *Network) AgentConfig() nodes.AgentConfigOption {
extraArgs := []any{} return opt.Config(func(k *koanf.Koanf) {
var urls, backends []string
if len(n.RelayNodes) > 0 { for _, r := range n.RelayNodes {
// TODO: We currently assume that all relays use the same credentials for _, u := range r.URLs() {
extraArgs = append(extraArgs, urls = append(urls, u.String())
"--ice-username", n.RelayNodes[0].Username(), }
"--ice-password", n.RelayNodes[0].Password(),
)
}
for _, r := range n.RelayNodes {
for _, u := range r.URLs() {
extraArgs = append(extraArgs, "--ice-url", u)
} }
}
for _, s := range n.SignalingNodes { for _, m := range n.SignalingNodes {
extraArgs = append(extraArgs, "--backend", s.URL()) u := m.URL()
} backends = append(backends, u.String())
}
return extraArgs k.Set("ice.urls", urls) //nolint:errcheck
k.Set("backends", backends) //nolint:errcheck
})
} }
func (n *Network) Close() { func (n *Network) Close() {
@@ -184,16 +182,19 @@ func (n *Network) WriteSpecReport() {
func (n *Network) Init() { func (n *Network) Init() {
*n = Network{} *n = Network{}
cwd, err := os.Getwd()
Expect(err).To(Succeed())
n.Name = fmt.Sprintf("cunicu-%d", rand.Uint32()) //nolint:gosec n.Name = fmt.Sprintf("cunicu-%d", rand.Uint32()) //nolint:gosec
n.BasePath = filepath.Join(SpecName()...) n.BasePath = filepath.Join(SpecName()...)
n.BasePath = filepath.Join("logs", n.BasePath) n.BasePath = filepath.Join(cwd, "logs", n.BasePath)
logFilename := filepath.Join(n.BasePath, "test.log") logFilename := filepath.Join(n.BasePath, "test.log")
pcapFilename := filepath.Join(n.BasePath, "capture.pcapng") pcapFilename := filepath.Join(n.BasePath, "capture.pcapng")
By("Tweaking sysctls for large Gont networks") By("Tweaking sysctls for large Gont networks")
err := osx.SetSysctlMap(map[string]any{ err = osx.SetSysctlMap(map[string]any{
"net.ipv4.neigh.default.gc_thresh1": 10000, "net.ipv4.neigh.default.gc_thresh1": 10000,
"net.ipv4.neigh.default.gc_thresh2": 15000, "net.ipv4.neigh.default.gc_thresh2": 15000,
"net.ipv4.neigh.default.gc_thresh3": 20000, "net.ipv4.neigh.default.gc_thresh3": 20000,

View File

@@ -11,6 +11,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/v2"
g "github.com/stv0g/gont/v2/pkg" g "github.com/stv0g/gont/v2/pkg"
copt "github.com/stv0g/gont/v2/pkg/options/cmd" copt "github.com/stv0g/gont/v2/pkg/options/cmd"
"go.uber.org/zap" "go.uber.org/zap"
@@ -20,12 +22,17 @@ import (
"github.com/stv0g/cunicu/pkg/log" "github.com/stv0g/cunicu/pkg/log"
rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc" rpcproto "github.com/stv0g/cunicu/pkg/proto/rpc"
"github.com/stv0g/cunicu/pkg/rpc" "github.com/stv0g/cunicu/pkg/rpc"
"github.com/stv0g/cunicu/test/e2e/nodes/options"
) )
type AgentOption interface { type AgentOption interface {
Apply(a *Agent) Apply(a *Agent)
} }
type AgentConfigOption interface {
Apply(k *koanf.Koanf)
}
// Agent is a host running the cunīcu daemon. // Agent is a host running the cunīcu daemon.
// //
// Each agent can have one or more WireGuard interfaces configured which are managed // Each agent can have one or more WireGuard interfaces configured which are managed
@@ -33,17 +40,15 @@ type AgentOption interface {
type Agent struct { type Agent struct {
*g.Host *g.Host
Command *g.Cmd Config *koanf.Koanf
Client *rpc.Client Command *g.Cmd
Client *rpc.Client
WireGuardClient *wgctrl.Client WireGuardClient *wgctrl.Client
ExtraArgs []any
WireGuardInterfaces []*WireGuardInterface WireGuardInterfaces []*WireGuardInterface
logFile io.WriteCloser logFile io.WriteCloser
logger *log.Logger
logger *log.Logger
} }
func NewAgent(m *g.Network, name string, opts ...g.Option) (*Agent, error) { func NewAgent(m *g.Network, name string, opts ...g.Option) (*Agent, error) {
@@ -53,15 +58,27 @@ func NewAgent(m *g.Network, name string, opts ...g.Option) (*Agent, error) {
} }
a := &Agent{ a := &Agent{
Host: h, Host: h,
Config: koanf.New("."),
logger: log.Global.Named("node.agent").With(zap.String("node", name)), logger: log.Global.Named("node.agent").With(zap.String("node", name)),
} }
// Default config
options.ConfigMap{
"experimental": true,
"log.level": "debug10",
"rpc.wait": true,
"sync_hosts": false,
}.Apply(a.Config)
// Apply agent options // Apply agent options
for _, opt := range opts { for _, opt := range opts {
if aopt, ok := opt.(AgentOption); ok { switch aopt := opt.(type) {
case AgentOption:
aopt.Apply(a) aopt.Apply(a)
case AgentConfigOption:
aopt.Apply(a.Config)
} }
} }
@@ -76,19 +93,32 @@ func NewAgent(m *g.Network, name string, opts ...g.Option) (*Agent, error) {
return a, nil return a, nil
} }
func (a *Agent) Start(_, dir string, extraArgs ...any) error { func (a *Agent) Start(_, dir string, args ...any) (err error) {
var err error cfgPath := fmt.Sprintf("%s/%s.yaml", dir, a.Name())
rpcSockPath := fmt.Sprintf("/var/run/cunicu.%s.sock", a.Name())
logPath := fmt.Sprintf("%s/%s.log", dir, a.Name()) logPath := fmt.Sprintf("%s/%s.log", dir, a.Name())
rpcPath := fmt.Sprintf("/var/run/cunicu.%s.sock", a.Name())
// Create agent configuration
cfg := a.Config.Copy()
cfg.Set("rpc.socket", rpcPath) //nolint:errcheck
extraArgs := []any{}
for _, arg := range args {
if aopt, ok := arg.(AgentConfigOption); ok {
aopt.Apply(cfg)
} else {
extraArgs = append(extraArgs, arg)
}
}
// Old RPC sockets are also removed by cunīcu. // Old RPC sockets are also removed by cunīcu.
// However we also need to do it here to avoid racing // However we also need to do it here to avoid racing
// against rpc.Connect() further down here // against rpc.Connect() further down here
if err := os.RemoveAll(rpcSockPath); err != nil { if err := os.RemoveAll(rpcPath); err != nil {
return fmt.Errorf("failed to remove old socket: %w", err) return fmt.Errorf("failed to remove old socket: %w", err)
} }
binary, profileArgs, err := BuildBinary(a.Name()) binary, startArgs, err := BuildBinary(a.Name())
if err != nil { if err != nil {
return fmt.Errorf("failed to build: %w", err) return fmt.Errorf("failed to build: %w", err)
} }
@@ -98,27 +128,28 @@ func (a *Agent) Start(_, dir string, extraArgs ...any) error {
return fmt.Errorf("failed to open log file: %w", err) return fmt.Errorf("failed to open log file: %w", err)
} }
args := profileArgs cfgRaw, err := cfg.Marshal(yaml.Parser())
args = append(args, if err != nil {
"daemon", return fmt.Errorf("failed to marshal config: %w", err)
"--rpc-socket", rpcSockPath, }
"--rpc-wait",
"--log-level", "debug5",
"--sync-hosts=false",
)
args = append(args, a.ExtraArgs...)
args = append(args, extraArgs...)
args = append(args,
copt.Combined(a.logFile),
copt.Dir(dir),
copt.EnvVar("CUNICU_EXPERIMENTAL", "1"),
)
if a.Command, err = a.Host.Start(binary, args...); err != nil { if err := os.WriteFile(cfgPath, cfgRaw, 0o644); err != nil { //nolint:gosec
return fmt.Errorf("failed to write config file: %w", err)
}
startArgs = append(startArgs,
copt.Combined(a.logFile),
copt.EnvVar("CUNICU_CONFIG_ALLOW_INSECURE", "true"),
copt.Dir(dir),
"daemon", "--config", cfgPath,
)
startArgs = append(startArgs, extraArgs...)
if a.Command, err = a.Host.Start(binary, startArgs...); err != nil {
return fmt.Errorf("failed to start: %w", err) return fmt.Errorf("failed to start: %w", err)
} }
if a.Client, err = rpc.Connect(rpcSockPath); err != nil { if a.Client, err = rpc.Connect(rpcPath); err != nil {
return fmt.Errorf("failed to connect to to control socket: %w", err) return fmt.Errorf("failed to connect to to control socket: %w", err)
} }

View File

@@ -15,9 +15,9 @@ import (
type AgentList []*Agent type AgentList []*Agent
func (al AgentList) Start(dir string, extraArgs ...any) error { func (al AgentList) Start(dir string, args ...any) error {
if err := al.ForEachAgent(func(a *Agent) error { if err := al.ForEachAgent(func(a *Agent) error {
return a.Start("", dir, extraArgs...) return a.Start("", dir, args...)
}); err != nil { }); err != nil {
return fmt.Errorf("failed to start agent: %w", err) return fmt.Errorf("failed to start agent: %w", err)
} }

View File

@@ -4,6 +4,7 @@
package nodes package nodes
import ( import (
"errors"
"flag" "flag"
"fmt" "fmt"
"os" "os"
@@ -31,6 +32,8 @@ var (
binaryPath string binaryPath string
binaryRunArgs []string binaryRunArgs []string
binaryOnce sync.Once binaryOnce sync.Once
errBuild = errors.New("failed to build")
) )
func BuildBinary(name string) (string, []any, error) { func BuildBinary(name string) (string, []any, error) {
@@ -130,7 +133,7 @@ func buildBinary(packagePath string) (string, []string, error) {
zap.Bool("test", test)) zap.Bool("test", test))
if output, err := exec.Command("go", cmdArgs...).CombinedOutput(); err != nil { if output, err := exec.Command("go", cmdArgs...).CombinedOutput(); err != nil {
return "", nil, fmt.Errorf("failed to build %s:\n\nError:\n%s\n\nOutput:\n%s", packagePath, path, string(output)) return "", nil, fmt.Errorf("%w %s:\n\nError:\n%s\n\nOutput:\n%s", errBuild, packagePath, path, string(output))
} }
logger.Debug("Finished building", logger.Debug("Finished building",

View File

@@ -1,14 +0,0 @@
// SPDX-FileCopyrightText: 2023 Steffen Vogel <post@steffenvogel.de>
// SPDX-License-Identifier: Apache-2.0
package options
import (
"github.com/stv0g/cunicu/test/e2e/nodes"
)
type ExtraArgs []any
func (ea ExtraArgs) Apply(a *nodes.Agent) {
a.ExtraArgs = append(a.ExtraArgs, ea...)
}

View File

@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2023 Steffen Vogel <post@steffenvogel.de>
// SPDX-License-Identifier: Apache-2.0
package options
import (
"github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/v2"
"github.com/stv0g/cunicu/pkg/config"
)
type Config func(k *koanf.Koanf)
func (c Config) Apply(k *koanf.Koanf) {
c(k)
}
type ConfigMap map[string]any
func (m ConfigMap) Apply(k *koanf.Koanf) {
p := confmap.Provider(m, ".")
k.Load(p, nil) //nolint:errcheck
}
func ConfigValue(key string, value any) Config {
return func(k *koanf.Koanf) {
k.Set(key, value) //nolint:errcheck
}
}
func ConfigStruct(s *config.Settings) Config {
return func(k *koanf.Koanf) {
p := config.NewStructsProvider(s, "koanf")
k.Load(p, nil) //nolint:errcheck
}
}

View File

@@ -4,7 +4,8 @@
package nodes package nodes
import ( import (
"github.com/pion/stun" "net/url"
g "github.com/stv0g/gont/v2/pkg" g "github.com/stv0g/gont/v2/pkg"
) )
@@ -12,9 +13,7 @@ type RelayNode interface {
Node Node
WaitReady() error WaitReady() error
URLs() []*stun.URI URLs() []url.URL
Username() string
Password() string
// Options // Options
Apply(i *g.Interface) Apply(i *g.Interface)

View File

@@ -7,6 +7,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"net/url"
"os" "os"
"os/exec" "os/exec"
"strconv" "strconv"
@@ -22,6 +23,11 @@ import (
var errTimeout = errors.New("timed out") var errTimeout = errors.New("timed out")
const (
relayUsername = "user1"
relayPassword = "password1"
)
type CoturnNode struct { type CoturnNode struct {
*g.Host *g.Host
@@ -55,23 +61,14 @@ func NewCoturnNode(n *g.Network, name string, opts ...g.Option) (*CoturnNode, er
"listening-port": strconv.Itoa(stun.DefaultPort), "listening-port": strconv.Itoa(stun.DefaultPort),
"realm": "cunicu", "realm": "cunicu",
"cli-password": "cunicu", "cli-password": "cunicu",
"user": fmt.Sprintf("%s:%s", relayUsername, relayPassword),
}, },
logger: log.Global.Named("node.relay").With(zap.String("node", name)), logger: log.Global.Named("node.relay").With(zap.String("node", name)),
} }
t.Config["user"] = fmt.Sprintf("%s:%s", t.Username(), t.Password())
return t, nil return t, nil
} }
func (c *CoturnNode) Username() string {
return "user1"
}
func (c *CoturnNode) Password() string {
return "password1"
}
func (c *CoturnNode) Start(_, dir string, extraArgs ...any) error { func (c *CoturnNode) Start(_, dir string, extraArgs ...any) error {
var err error var err error
@@ -151,27 +148,25 @@ func (c *CoturnNode) WaitReady() error {
return nil return nil
} }
func (c *CoturnNode) URLs() []*stun.URI { func (c *CoturnNode) URLs() []url.URL {
host := c.Name() host := c.Name()
hostPort := fmt.Sprintf("%s:%d", host, stun.DefaultPort)
userHostPort := fmt.Sprintf("%s:%s@%s", relayUsername, relayPassword, hostPort)
return []*stun.URI{ return []url.URL{
{ {
Scheme: stun.SchemeTypeSTUN, Scheme: "stun",
Host: host, Opaque: hostPort,
Port: stun.DefaultPort,
Proto: stun.ProtoTypeUDP,
}, },
{ {
Scheme: stun.SchemeTypeTURN, Scheme: "turn",
Host: host, Opaque: userHostPort,
Port: stun.DefaultPort, RawQuery: "transport=udp",
Proto: stun.ProtoTypeUDP,
}, },
{ {
Scheme: stun.SchemeTypeTURN, Scheme: "turn",
Host: host, Opaque: userHostPort,
Port: stun.DefaultPort, RawQuery: "transport=tcp",
Proto: stun.ProtoTypeTCP,
}, },
} }
} }

View File

@@ -8,5 +8,5 @@ import "net/url"
type SignalingNode interface { type SignalingNode interface {
Node Node
URL() *url.URL URL() url.URL
} }

View File

@@ -103,10 +103,12 @@ func (s *GrpcSignalingNode) Close() error {
return s.Stop() return s.Stop()
} }
func (s *GrpcSignalingNode) URL() *url.URL { func (s *GrpcSignalingNode) URL() url.URL {
return &url.URL{ hostPort := fmt.Sprintf("%s:%d", s.Name(), s.port)
return url.URL{
Scheme: "grpc", Scheme: "grpc",
Host: fmt.Sprintf("%s:%d", s.Name(), s.port), Host: hostPort,
RawQuery: "insecure=true", RawQuery: "insecure=true",
} }
} }

View File

@@ -60,7 +60,7 @@ var _ = Context("restart: Restart ICE agents", func() {
n.AgentOptions = append(n.AgentOptions, n.AgentOptions = append(n.AgentOptions,
gopt.EmptyDir(wg.ConfigPath), gopt.EmptyDir(wg.ConfigPath),
gopt.EmptyDir(wg.SocketPath), gopt.EmptyDir(wg.SocketPath),
opt.ExtraArgs{"--ice-candidate-type", "host"}, opt.ConfigValue("ice.candidate_types", "host"),
) )
n.WireGuardInterfaceOptions = append(n.WireGuardInterfaceOptions, n.WireGuardInterfaceOptions = append(n.WireGuardInterfaceOptions,
@@ -170,7 +170,7 @@ var _ = Context("restart: Restart ICE agents", func() {
By("Re-starting first agent again") By("Re-starting first agent again")
err = n1.Start("", n.BasePath, n.AgentArgs()...) err = n1.Start("", n.BasePath, n.AgentConfig())
Expect(err).To(Succeed(), "Failed to restart first agent: %s", err) Expect(err).To(Succeed(), "Failed to restart first agent: %s", err)
}) })
}) })

View File

@@ -12,6 +12,7 @@ import (
gfopt "github.com/stv0g/gont/v2/pkg/options/filters" gfopt "github.com/stv0g/gont/v2/pkg/options/filters"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"github.com/stv0g/cunicu/pkg/config"
"github.com/stv0g/cunicu/pkg/wg" "github.com/stv0g/cunicu/pkg/wg"
"github.com/stv0g/cunicu/test" "github.com/stv0g/cunicu/test"
"github.com/stv0g/cunicu/test/e2e/nodes" "github.com/stv0g/cunicu/test/e2e/nodes"
@@ -140,7 +141,9 @@ var _ = Context("simple: Simple local-area switched topology with variable numbe
) )
n.AgentOptions = append(n.AgentOptions, n.AgentOptions = append(n.AgentOptions,
opt.ExtraArgs{"--wg-userspace", "wg0"}, opt.ConfigValue("interfaces.wg0", config.InterfaceSettings{
UserSpace: true,
}),
) )
}) })
@@ -189,7 +192,7 @@ var _ = Context("simple: Simple local-area switched topology with variable numbe
Context("pdisc: Peer Discovery", Pending, Ordered, func() { Context("pdisc: Peer Discovery", Pending, Ordered, func() {
BeforeEach(OncePerOrdered, func() { BeforeEach(OncePerOrdered, func() {
n.AgentOptions = append(n.AgentOptions, n.AgentOptions = append(n.AgentOptions,
opt.ExtraArgs{"--community", "hallo"}, opt.ConfigValue("community", "hallo"),
) )
n.WireGuardInterfaceOptions = append(n.WireGuardInterfaceOptions, n.WireGuardInterfaceOptions = append(n.WireGuardInterfaceOptions,