Pulse/pkg/discovery/envdetect/detect_environment_test.go

287 lines
8.5 KiB
Go

package envdetect
import (
"errors"
"net"
"os"
"os/exec"
"strings"
"testing"
"time"
)
type fakeFileInfo struct{}
func (fakeFileInfo) Name() string { return "fake" }
func (fakeFileInfo) Size() int64 { return 0 }
func (fakeFileInfo) Mode() os.FileMode { return 0 }
func (fakeFileInfo) ModTime() (t time.Time) {
return t
}
func (fakeFileInfo) IsDir() bool { return false }
func (fakeFileInfo) Sys() interface{} { return nil }
type fakeEnvironmentProbe struct {
lookPathPresent map[string]bool
commandOutput map[string][]byte
commandErr map[string]error
fileData map[string][]byte
fileErr map[string]error
statPresent map[string]bool
interfaces []ifaceInfo
interfacesErr error
}
func (p fakeEnvironmentProbe) LookPath(file string) (string, error) {
if p.lookPathPresent[file] {
return "/usr/bin/" + file, nil
}
return "", &exec.Error{Name: file, Err: exec.ErrNotFound}
}
func (p fakeEnvironmentProbe) CommandCombinedOutput(name string, args ...string) ([]byte, error) {
key := name + "\x00" + strings.Join(args, "\x00")
if out, ok := p.commandOutput[key]; ok {
return out, p.commandErr[key]
}
return nil, errors.New("unexpected command invocation")
}
func (p fakeEnvironmentProbe) Stat(name string) (os.FileInfo, error) {
if p.statPresent[name] {
return fakeFileInfo{}, nil
}
return nil, &os.PathError{Op: "stat", Path: name, Err: os.ErrNotExist}
}
func (p fakeEnvironmentProbe) ReadFile(name string) ([]byte, error) {
if err, ok := p.fileErr[name]; ok && err != nil {
return nil, err
}
if data, ok := p.fileData[name]; ok {
return data, nil
}
return nil, &os.PathError{Op: "open", Path: name, Err: os.ErrNotExist}
}
func (p fakeEnvironmentProbe) Interfaces() ([]ifaceInfo, error) {
if p.interfacesErr != nil {
return nil, p.interfacesErr
}
return p.interfaces, nil
}
func mustIPNet(t *testing.T, cidr string) *net.IPNet {
t.Helper()
_, network, err := net.ParseCIDR(cidr)
if err != nil {
t.Fatalf("net.ParseCIDR(%q): %v", cidr, err)
}
return network
}
func TestDetectEnvironment_Native(t *testing.T) {
probe := fakeEnvironmentProbe{
interfaces: []ifaceInfo{
{
Name: "eth0",
Flags: net.FlagUp,
Addrs: []net.Addr{
&net.IPNet{IP: net.IPv4(192, 168, 1, 10), Mask: net.CIDRMask(24, 32)},
},
},
},
}
profile, err := detectEnvironment(probe)
if err != nil {
t.Fatalf("detectEnvironment returned error: %v", err)
}
if profile.Type != Native {
t.Fatalf("Type = %v, want %v", profile.Type, Native)
}
if profile.Metadata["container_detected"] != "false" {
t.Fatalf("container_detected = %q, want false", profile.Metadata["container_detected"])
}
if len(profile.Phases) != 1 || profile.Phases[0].Name != "local_networks" {
t.Fatalf("Phases = %#v, want single local_networks phase", profile.Phases)
}
if got := profile.Phases[0].Subnets[0].String(); got != mustIPNet(t, "192.168.1.0/24").String() {
t.Fatalf("subnet = %q, want %q", got, "192.168.1.0/24")
}
}
func TestDetectEnvironment_DockerHostMode(t *testing.T) {
route := strings.Join([]string{
"Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\tMTU\tWindow\tIRTT",
"lo\t00000000\t00000000\t0003\t0\t0\t0\t00000000\t0\t0\t0",
"eth0\t00000000\t010011AC\t0003\t0\t0\t0\t00000000\t0\t0\t0",
"eth0\t000011AC\t00000000\t0001\t0\t0\t0\t0000FFFF\t0\t0\t0",
"eth1\t00000000\t010011AC\t0003\t0\t0\t0\t00000000\t0\t0\t0",
"eth2\t00000000\t010011AC\t0003\t0\t0\t0\t00000000\t0\t0\t0",
"eth3\t00000000\t010011AC\t0003\t0\t0\t0\t00000000\t0\t0\t0",
}, "\n")
probe := fakeEnvironmentProbe{
statPresent: map[string]bool{"/.dockerenv": true},
fileData: map[string][]byte{"/proc/net/route": []byte(route)},
interfaces: []ifaceInfo{
{Name: "lo", Flags: net.FlagUp | net.FlagLoopback},
{
Name: "eth0",
Flags: net.FlagUp,
Addrs: []net.Addr{
&net.IPNet{IP: net.IPv4(10, 0, 0, 10), Mask: net.CIDRMask(24, 32)},
},
},
{Name: "eth1", Flags: net.FlagUp},
{Name: "eth2", Flags: net.FlagUp},
},
}
profile, err := detectEnvironment(probe)
if err != nil {
t.Fatalf("detectEnvironment returned error: %v", err)
}
if profile.Type != DockerHost {
t.Fatalf("Type = %v, want %v", profile.Type, DockerHost)
}
if profile.Metadata["docker_mode"] != "host" {
t.Fatalf("docker_mode = %q, want host", profile.Metadata["docker_mode"])
}
if len(profile.Phases) != 1 || profile.Phases[0].Name != "host_networks" {
t.Fatalf("Phases = %#v, want single host_networks phase", profile.Phases)
}
if got := profile.Phases[0].Subnets[0].String(); got != mustIPNet(t, "10.0.0.0/24").String() {
t.Fatalf("subnet = %q, want %q", got, "10.0.0.0/24")
}
}
func TestDetectEnvironment_DockerBridge_InferredHostNetwork(t *testing.T) {
route := strings.Join([]string{
"Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\tMTU\tWindow\tIRTT",
"eth0\t00000000\t010011AC\t0003\t0\t0\t0\t00000000\t0\t0\t0",
"eth0\t000011AC\t00000000\t0001\t0\t0\t0\t0000FFFF\t0\t0\t0",
}, "\n")
probe := fakeEnvironmentProbe{
statPresent: map[string]bool{"/.dockerenv": true},
fileData: map[string][]byte{"/proc/net/route": []byte(route)},
interfaces: []ifaceInfo{
{
Name: "eth0",
Flags: net.FlagUp,
Addrs: []net.Addr{
&net.IPNet{IP: net.IPv4(172, 17, 0, 2), Mask: net.CIDRMask(16, 32)},
},
},
},
}
profile, err := detectEnvironment(probe)
if err != nil {
t.Fatalf("detectEnvironment returned error: %v", err)
}
if profile.Type != DockerBridge {
t.Fatalf("Type = %v, want %v", profile.Type, DockerBridge)
}
var containerFound, inferredFound bool
for _, phase := range profile.Phases {
switch phase.Name {
case "container_network":
containerFound = true
if got := phase.Subnets[0].String(); got != mustIPNet(t, "172.17.0.0/16").String() {
t.Fatalf("container subnet = %q, want %q", got, "172.17.0.0/16")
}
case "inferred_host_network":
inferredFound = true
if got := phase.Subnets[0].String(); got != mustIPNet(t, "172.17.0.0/24").String() {
t.Fatalf("inferred subnet = %q, want %q", got, "172.17.0.0/24")
}
if phase.Confidence != 0.7 {
t.Fatalf("inferred confidence = %v, want 0.7", phase.Confidence)
}
}
}
if !containerFound || !inferredFound {
t.Fatalf("expected both container_network and inferred_host_network phases, got %#v", profile.Phases)
}
}
func TestDetectEnvironment_LXCPrivileged_SystemdDetectVirt(t *testing.T) {
probe := fakeEnvironmentProbe{
lookPathPresent: map[string]bool{"systemd-detect-virt": true},
commandOutput: map[string][]byte{
"systemd-detect-virt\x00--container": []byte("lxc\n"),
},
fileData: map[string][]byte{
"/proc/self/uid_map": []byte("0 0 4294967295\n"),
},
interfaces: []ifaceInfo{
{
Name: "eth0",
Flags: net.FlagUp,
Addrs: []net.Addr{
&net.IPNet{IP: net.IPv4(192, 168, 50, 10), Mask: net.CIDRMask(24, 32)},
},
},
},
}
profile, err := detectEnvironment(probe)
if err != nil {
t.Fatalf("detectEnvironment returned error: %v", err)
}
if profile.Type != LXCPrivileged {
t.Fatalf("Type = %v, want %v", profile.Type, LXCPrivileged)
}
if profile.Metadata["lxc_privileged"] != "true" {
t.Fatalf("lxc_privileged = %q, want true", profile.Metadata["lxc_privileged"])
}
if len(profile.Phases) != 1 || profile.Phases[0].Name != "lxc_host_networks" {
t.Fatalf("Phases = %#v, want single lxc_host_networks phase", profile.Phases)
}
}
func TestGetDefaultGateway_DefaultRouteNotFound(t *testing.T) {
route := strings.Join([]string{
"Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\tMTU\tWindow\tIRTT",
"eth0\t000011AC\t00000000\t0001\t0\t0\t0\t0000FFFF\t0\t0\t0",
}, "\n")
probe := fakeEnvironmentProbe{
fileData: map[string][]byte{"/proc/net/route": []byte(route)},
}
_, err := getDefaultGateway(probe)
if err == nil {
t.Fatalf("expected error, got nil")
}
if !strings.Contains(err.Error(), "default gateway not found") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestCountKernelRoutes_SkipsHeaderAndBlankLines(t *testing.T) {
route := strings.Join([]string{
"Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\tMTU\tWindow\tIRTT",
"",
"eth0\t00000000\t010011AC\t0003\t0\t0\t0\t00000000\t0\t0\t0",
"eth0\t000011AC\t00000000\t0001\t0\t0\t0\t0000FFFF\t0\t0\t0",
" ",
}, "\n")
probe := fakeEnvironmentProbe{
fileData: map[string][]byte{"/proc/net/route": []byte(route)},
}
count, warn := countKernelRoutes(probe)
if warn != "" {
t.Fatalf("warn = %q, want empty", warn)
}
if count != 2 {
t.Fatalf("count = %d, want 2", count)
}
}