open-consul/agent/consul/util_test.go
Daniel Nephin ea6c2b2adc ci: Add staticcheck and fix most errors
Three of the checks are temporarily disabled to limit the size of the
diff, and allow us to enable all the other checks in CI.

In a follow up we can fix the issues reported by the other checks one
at a time, and enable them.
2020-05-28 11:59:58 -04:00

722 lines
17 KiB
Go

package consul
import (
"errors"
"fmt"
"net"
"regexp"
"testing"
"github.com/hashicorp/consul/agent/metadata"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/go-version"
"github.com/hashicorp/serf/serf"
"github.com/stretchr/testify/require"
)
func TestGetPrivateIP(t *testing.T) {
t.Parallel()
ip, _, err := net.ParseCIDR("10.1.2.3/32")
if err != nil {
t.Fatalf("failed to parse private cidr: %v", err)
}
pubIP, _, err := net.ParseCIDR("8.8.8.8/32")
if err != nil {
t.Fatalf("failed to parse public cidr: %v", err)
}
tests := []struct {
addrs []net.Addr
expected net.IP
err error
}{
{
addrs: []net.Addr{
&net.IPAddr{
IP: ip,
},
&net.IPAddr{
IP: pubIP,
},
},
expected: ip,
},
{
addrs: []net.Addr{
&net.IPAddr{
IP: pubIP,
},
},
err: errors.New("No private IP address found"),
},
{
addrs: []net.Addr{
&net.IPAddr{
IP: ip,
},
&net.IPAddr{
IP: ip,
},
&net.IPAddr{
IP: pubIP,
},
},
err: errors.New("Multiple private IPs found. Please configure one."),
},
}
for _, test := range tests {
ip, err := getPrivateIP(test.addrs)
switch {
case test.err != nil && err != nil:
if err.Error() != test.err.Error() {
t.Fatalf("unexpected error: %v != %v", test.err, err)
}
case (test.err == nil && err != nil) || (test.err != nil && err == nil):
t.Fatalf("unexpected error: %v != %v", test.err, err)
default:
if !test.expected.Equal(ip) {
t.Fatalf("unexpected ip: %v != %v", ip, test.expected)
}
}
}
}
func TestIsPrivateIP(t *testing.T) {
t.Parallel()
if !isPrivateIP("192.168.1.1") {
t.Fatalf("bad")
}
if !isPrivateIP("172.16.45.100") {
t.Fatalf("bad")
}
if !isPrivateIP("10.1.2.3") {
t.Fatalf("bad")
}
if !isPrivateIP("100.115.110.19") {
t.Fatalf("bad")
}
if isPrivateIP("8.8.8.8") {
t.Fatalf("bad")
}
if !isPrivateIP("127.0.0.1") {
t.Fatalf("bad")
}
}
func TestUtil_CanServersUnderstandProtocol(t *testing.T) {
t.Parallel()
var members []serf.Member
// All empty list cases should return false.
for v := ProtocolVersionMin; v <= ProtocolVersionMax; v++ {
grok, err := CanServersUnderstandProtocol(members, v)
if err != nil {
t.Fatalf("err: %v", err)
}
if grok {
t.Fatalf("empty list should always return false")
}
}
// Add a non-server member.
members = append(members, serf.Member{
Tags: map[string]string{
"vsn_min": fmt.Sprintf("%d", ProtocolVersionMin),
"vsn_max": fmt.Sprintf("%d", ProtocolVersionMax),
},
})
// Make sure it doesn't get counted.
for v := ProtocolVersionMin; v <= ProtocolVersionMax; v++ {
grok, err := CanServersUnderstandProtocol(members, v)
if err != nil {
t.Fatalf("err: %v", err)
}
if grok {
t.Fatalf("non-server members should not be counted")
}
}
// Add a server member.
members = append(members, serf.Member{
Tags: map[string]string{
"role": "consul",
"vsn_min": fmt.Sprintf("%d", ProtocolVersionMin),
"vsn_max": fmt.Sprintf("%d", ProtocolVersionMax),
},
})
// Now it should report that it understands.
for v := ProtocolVersionMin; v <= ProtocolVersionMax; v++ {
grok, err := CanServersUnderstandProtocol(members, v)
if err != nil {
t.Fatalf("err: %v", err)
}
if !grok {
t.Fatalf("server should grok")
}
}
// Nobody should understand anything from the future.
for v := uint8(ProtocolVersionMax + 1); v <= uint8(ProtocolVersionMax+10); v++ {
grok, err := CanServersUnderstandProtocol(members, v)
if err != nil {
t.Fatalf("err: %v", err)
}
if grok {
t.Fatalf("server should not grok")
}
}
// Add an older server.
members = append(members, serf.Member{
Tags: map[string]string{
"role": "consul",
"vsn_min": fmt.Sprintf("%d", ProtocolVersionMin),
"vsn_max": fmt.Sprintf("%d", ProtocolVersionMax-1),
},
})
// The servers should no longer understand the max version.
for v := ProtocolVersionMin; v <= ProtocolVersionMax; v++ {
grok, err := CanServersUnderstandProtocol(members, v)
if err != nil {
t.Fatalf("err: %v", err)
}
expected := v < ProtocolVersionMax
if grok != expected {
t.Fatalf("bad: %v != %v", grok, expected)
}
}
// Try a version that's too low for the minimum.
{
grok, err := CanServersUnderstandProtocol(members, 0)
if err != nil {
t.Fatalf("err: %v", err)
}
if grok {
t.Fatalf("server should not grok")
}
}
}
func TestIsConsulNode(t *testing.T) {
t.Parallel()
m := serf.Member{
Tags: map[string]string{
"role": "node",
"dc": "east-aws",
},
}
valid, dc := isConsulNode(m)
if !valid || dc != "east-aws" {
t.Fatalf("bad: %v %v", valid, dc)
}
}
func TestByteConversion(t *testing.T) {
t.Parallel()
var val uint64 = 2 << 50
raw := uint64ToBytes(val)
if bytesToUint64(raw) != val {
t.Fatalf("no match")
}
}
func TestGenerateUUID(t *testing.T) {
t.Parallel()
prev := generateUUID()
re, err := regexp.Compile("[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}")
require.NoError(t, err)
for i := 0; i < 100; i++ {
id := generateUUID()
if prev == id {
t.Fatalf("Should get a new ID!")
}
matched := re.MatchString(id)
if !matched {
t.Fatalf("expected match %s %v %s", id, matched, err)
}
}
}
func TestGetPublicIPv6(t *testing.T) {
t.Parallel()
ip, _, err := net.ParseCIDR("fe80::1/128")
if err != nil {
t.Fatalf("failed to parse link-local cidr: %v", err)
}
ip2, _, err := net.ParseCIDR("::1/128")
if err != nil {
t.Fatalf("failed to parse loopback cidr: %v", err)
}
ip3, _, err := net.ParseCIDR("fc00::1/128")
if err != nil {
t.Fatalf("failed to parse ULA cidr: %v", err)
}
pubIP, _, err := net.ParseCIDR("2001:0db8:85a3::8a2e:0370:7334/128")
if err != nil {
t.Fatalf("failed to parse public cidr: %v", err)
}
tests := []struct {
addrs []net.Addr
expected net.IP
err error
}{
{
addrs: []net.Addr{
&net.IPAddr{
IP: ip,
},
&net.IPAddr{
IP: ip2,
},
&net.IPAddr{
IP: ip3,
},
&net.IPAddr{
IP: pubIP,
},
},
expected: pubIP,
},
{
addrs: []net.Addr{
&net.IPAddr{
IP: ip,
},
&net.IPAddr{
IP: ip2,
},
&net.IPAddr{
IP: ip3,
},
},
err: errors.New("No public IPv6 address found"),
},
{
addrs: []net.Addr{
&net.IPAddr{
IP: ip,
},
&net.IPAddr{
IP: ip,
},
&net.IPAddr{
IP: pubIP,
},
&net.IPAddr{
IP: pubIP,
},
},
err: errors.New("Multiple public IPv6 addresses found. Please configure one."),
},
}
for _, test := range tests {
ip, err := getPublicIPv6(test.addrs)
switch {
case test.err != nil && err != nil:
if err.Error() != test.err.Error() {
t.Fatalf("unexpected error: %v != %v", test.err, err)
}
case (test.err == nil && err != nil) || (test.err != nil && err == nil):
t.Fatalf("unexpected error: %v != %v", test.err, err)
default:
if !test.expected.Equal(ip) {
t.Fatalf("unexpected ip: %v != %v", ip, test.expected)
}
}
}
}
type testServersProvider []metadata.Server
func (p testServersProvider) CheckServers(datacenter string, fn func(*metadata.Server) bool) {
for _, srv := range p {
// filter these out - now I don't have to modify the tests. Originally the dc filtering
// happened in the ServersInDCMeetMinimumVersion, now we get a list of servers to check
// through the routing infrastructure or server lookup which will map a datacenter to a
// list of metadata.Server structs that are all in that datacenter.
if srv.Datacenter != datacenter {
continue
}
if !fn(&srv) {
return
}
}
}
func TestServersInDCMeetMinimumVersion(t *testing.T) {
t.Parallel()
makeServer := func(versionStr string, datacenter string) metadata.Server {
return metadata.Server{
Name: "foo",
ShortName: "foo",
ID: "asdf",
Port: 10000,
Expect: 3,
RaftVersion: 3,
Status: serf.StatusAlive,
WanJoinPort: 1234,
Version: 1,
Build: *version.Must(version.NewVersion(versionStr)),
Datacenter: datacenter,
}
}
cases := []struct {
servers testServersProvider
ver *version.Version
expected bool
expectedFound bool
}{
// One server, meets reqs
{
servers: testServersProvider{
makeServer("0.7.5", "primary"),
makeServer("0.7.3", "secondary"),
},
ver: version.Must(version.NewVersion("0.7.5")),
expected: true,
expectedFound: true,
},
// One server, doesn't meet reqs
{
servers: testServersProvider{
makeServer("0.7.5", "primary"),
makeServer("0.8.1", "secondary"),
},
ver: version.Must(version.NewVersion("0.8.0")),
expected: false,
expectedFound: true,
},
// Multiple servers, meets req version
{
servers: testServersProvider{
makeServer("0.7.5", "primary"),
makeServer("0.8.0", "primary"),
makeServer("0.7.0", "secondary"),
},
ver: version.Must(version.NewVersion("0.7.5")),
expected: true,
expectedFound: true,
},
// Multiple servers, doesn't meet req version
{
servers: testServersProvider{
makeServer("0.7.5", "primary"),
makeServer("0.8.0", "primary"),
makeServer("0.9.1", "secondary"),
},
ver: version.Must(version.NewVersion("0.8.0")),
expected: false,
expectedFound: true,
},
{
servers: testServersProvider{
makeServer("0.7.5", "secondary"),
makeServer("0.8.0", "secondary"),
makeServer("0.9.1", "secondary"),
},
ver: version.Must(version.NewVersion("0.7.0")),
expected: true,
expectedFound: false,
},
}
for _, tc := range cases {
result, found := ServersInDCMeetMinimumVersion(tc.servers, "primary", tc.ver)
require.Equal(t, tc.expected, result)
require.Equal(t, tc.expectedFound, found)
}
}
func TestInterpolateHIL(t *testing.T) {
for name, test := range map[string]struct {
in string
vars map[string]string
exp string // when lower=false
expLower string // when lower=true
ok bool
}{
// valid HIL
"empty": {
"",
map[string]string{},
"",
"",
true,
},
"no vars": {
"nothing",
map[string]string{},
"nothing",
"nothing",
true,
},
"just lowercase var": {
"${item}",
map[string]string{"item": "value"},
"value",
"value",
true,
},
"just uppercase var": {
"${item}",
map[string]string{"item": "VaLuE"},
"VaLuE",
"value",
true,
},
"lowercase var in middle": {
"before ${item}after",
map[string]string{"item": "value"},
"before valueafter",
"before valueafter",
true,
},
"uppercase var in middle": {
"before ${item}after",
map[string]string{"item": "VaLuE"},
"before VaLuEafter",
"before valueafter",
true,
},
"two vars": {
"before ${item}after ${more}",
map[string]string{"item": "value", "more": "xyz"},
"before valueafter xyz",
"before valueafter xyz",
true,
},
"missing map val": {
"${item}",
map[string]string{"item": ""},
"",
"",
true,
},
// "weird" HIL, but not technically invalid
"just end": {
"}",
map[string]string{},
"}",
"}",
true,
},
"var without start": {
" item }",
map[string]string{"item": "value"},
" item }",
" item }",
true,
},
"two vars missing second start": {
"before ${ item }after more }",
map[string]string{"item": "value", "more": "xyz"},
"before valueafter more }",
"before valueafter more }",
true,
},
// invalid HIL
"just start": {
"${",
map[string]string{},
"",
"",
false,
},
"backwards": {
"}${",
map[string]string{},
"",
"",
false,
},
"no varname": {
"${}",
map[string]string{},
"",
"",
false,
},
"missing map key": {
"${item}",
map[string]string{},
"",
"",
false,
},
"var without end": {
"${ item ",
map[string]string{"item": "value"},
"",
"",
false,
},
"two vars missing first end": {
"before ${ item after ${ more }",
map[string]string{"item": "value", "more": "xyz"},
"",
"",
false,
},
} {
test := test
t.Run(name+" lower=false", func(t *testing.T) {
out, err := InterpolateHIL(test.in, test.vars, false)
if test.ok {
require.NoError(t, err)
require.Equal(t, test.exp, out)
} else {
require.NotNil(t, err)
require.Equal(t, out, "")
}
})
t.Run(name+" lower=true", func(t *testing.T) {
out, err := InterpolateHIL(test.in, test.vars, true)
if test.ok {
require.NoError(t, err)
require.Equal(t, test.expLower, out)
} else {
require.NotNil(t, err)
require.Equal(t, out, "")
}
})
}
}
func TestServersGetACLMode(t *testing.T) {
t.Parallel()
makeServer := func(datacenter string, acls structs.ACLMode, status serf.MemberStatus, addr net.IP) metadata.Server {
return metadata.Server{
Name: "foo",
ShortName: "foo",
ID: "asdf",
Port: 10000,
Expect: 3,
RaftVersion: 3,
Status: status,
WanJoinPort: 1234,
Version: 1,
Addr: &net.TCPAddr{IP: addr, Port: 10000},
// shouldn't matter for these tests
Build: *version.Must(version.NewVersion("1.7.0")),
Datacenter: datacenter,
ACLs: acls,
}
}
type tcase struct {
servers testServersProvider
leaderAddr string
datacenter string
foundServers bool
minMode structs.ACLMode
leaderMode structs.ACLMode
}
cases := map[string]tcase{
"filter-members": tcase{
servers: testServersProvider{
makeServer("primary", structs.ACLModeLegacy, serf.StatusAlive, net.IP([]byte{127, 0, 0, 1})),
makeServer("primary", structs.ACLModeLegacy, serf.StatusFailed, net.IP([]byte{127, 0, 0, 2})),
// filtered datacenter
makeServer("secondary", structs.ACLModeUnknown, serf.StatusAlive, net.IP([]byte{127, 0, 0, 4})),
// filtered status
makeServer("primary", structs.ACLModeUnknown, serf.StatusLeaving, net.IP([]byte{127, 0, 0, 5})),
// filtered status
makeServer("primary", structs.ACLModeUnknown, serf.StatusLeft, net.IP([]byte{127, 0, 0, 6})),
// filtered status
makeServer("primary", structs.ACLModeUnknown, serf.StatusNone, net.IP([]byte{127, 0, 0, 7})),
},
foundServers: true,
leaderAddr: "127.0.0.1:10000",
datacenter: "primary",
minMode: structs.ACLModeLegacy,
leaderMode: structs.ACLModeLegacy,
},
"disabled": tcase{
servers: testServersProvider{
makeServer("primary", structs.ACLModeLegacy, serf.StatusAlive, net.IP([]byte{127, 0, 0, 1})),
makeServer("primary", structs.ACLModeUnknown, serf.StatusAlive, net.IP([]byte{127, 0, 0, 2})),
makeServer("primary", structs.ACLModeDisabled, serf.StatusAlive, net.IP([]byte{127, 0, 0, 3})),
},
foundServers: true,
leaderAddr: "127.0.0.1:10000",
datacenter: "primary",
minMode: structs.ACLModeDisabled,
leaderMode: structs.ACLModeLegacy,
},
"unknown": tcase{
servers: testServersProvider{
makeServer("primary", structs.ACLModeLegacy, serf.StatusAlive, net.IP([]byte{127, 0, 0, 1})),
makeServer("primary", structs.ACLModeUnknown, serf.StatusAlive, net.IP([]byte{127, 0, 0, 2})),
},
foundServers: true,
leaderAddr: "127.0.0.1:10000",
datacenter: "primary",
minMode: structs.ACLModeUnknown,
leaderMode: structs.ACLModeLegacy,
},
"legacy": tcase{
servers: testServersProvider{
makeServer("primary", structs.ACLModeEnabled, serf.StatusAlive, net.IP([]byte{127, 0, 0, 1})),
makeServer("primary", structs.ACLModeLegacy, serf.StatusAlive, net.IP([]byte{127, 0, 0, 2})),
},
foundServers: true,
leaderAddr: "127.0.0.1:10000",
datacenter: "primary",
minMode: structs.ACLModeLegacy,
leaderMode: structs.ACLModeEnabled,
},
"enabled": tcase{
servers: testServersProvider{
makeServer("primary", structs.ACLModeEnabled, serf.StatusAlive, net.IP([]byte{127, 0, 0, 1})),
makeServer("primary", structs.ACLModeEnabled, serf.StatusAlive, net.IP([]byte{127, 0, 0, 2})),
makeServer("primary", structs.ACLModeEnabled, serf.StatusAlive, net.IP([]byte{127, 0, 0, 3})),
},
foundServers: true,
leaderAddr: "127.0.0.1:10000",
datacenter: "primary",
minMode: structs.ACLModeEnabled,
leaderMode: structs.ACLModeEnabled,
},
"failed-members": tcase{
servers: testServersProvider{
makeServer("primary", structs.ACLModeLegacy, serf.StatusAlive, net.IP([]byte{127, 0, 0, 1})),
makeServer("primary", structs.ACLModeUnknown, serf.StatusFailed, net.IP([]byte{127, 0, 0, 2})),
makeServer("primary", structs.ACLModeLegacy, serf.StatusFailed, net.IP([]byte{127, 0, 0, 3})),
},
foundServers: true,
leaderAddr: "127.0.0.1:10000",
datacenter: "primary",
minMode: structs.ACLModeUnknown,
leaderMode: structs.ACLModeLegacy,
},
}
for name, tc := range cases {
name := name
tc := tc
t.Run(name, func(t *testing.T) {
actualServers, actualMinMode, actualLeaderMode := ServersGetACLMode(tc.servers, tc.leaderAddr, tc.datacenter)
require.Equal(t, tc.minMode, actualMinMode)
require.Equal(t, tc.leaderMode, actualLeaderMode)
require.Equal(t, tc.foundServers, actualServers)
})
}
}