// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package consul

import (
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc"
	"github.com/hashicorp/consul-net-rpc/net/rpc"

	"github.com/hashicorp/consul/acl"
	"github.com/hashicorp/consul/agent/netutil"
	"github.com/hashicorp/consul/agent/structs"
	"github.com/hashicorp/consul/api"
	"github.com/hashicorp/consul/lib/stringslice"
	"github.com/hashicorp/consul/proto/private/pbpeering"
	"github.com/hashicorp/consul/sdk/testutil"
	"github.com/hashicorp/consul/sdk/testutil/retry"
	"github.com/hashicorp/consul/testrpc"
	"github.com/hashicorp/consul/types"
)

func TestInternal_NodeInfo(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	_, s1 := testServerWithConfig(t, func(config *Config) {
		config.PeeringTestAllowPeerRegistrations = true
	})
	codec := rpcClient(t, s1)

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	args := []*structs.RegisterRequest{
		{
			Datacenter: "dc1",
			Node:       "foo",
			Address:    "127.0.0.1",
			Service: &structs.NodeService{
				ID:      "db",
				Service: "db",
				Tags:    []string{"primary"},
			},
			Check: &structs.HealthCheck{
				Name:      "db connect",
				Status:    api.HealthPassing,
				ServiceID: "db",
			},
		},
		{
			Datacenter: "dc1",
			Node:       "foo",
			Address:    "127.0.0.3",
			PeerName:   "peer1",
		},
	}

	for _, reg := range args {
		err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", reg, nil)
		require.NoError(t, err)
	}

	t.Run("get local node", func(t *testing.T) {
		var out structs.IndexedNodeDump
		req := structs.NodeSpecificRequest{
			Datacenter: "dc1",
			Node:       "foo",
		}
		if err := msgpackrpc.CallWithCodec(codec, "Internal.NodeInfo", &req, &out); err != nil {
			t.Fatalf("err: %v", err)
		}

		nodes := out.Dump
		if len(nodes) != 1 {
			t.Fatalf("Bad: %v", nodes)
		}
		if nodes[0].Node != "foo" {
			t.Fatalf("Bad: %v", nodes[0])
		}
		if !stringslice.Contains(nodes[0].Services[0].Tags, "primary") {
			t.Fatalf("Bad: %v", nodes[0])
		}
		if nodes[0].Checks[0].Status != api.HealthPassing {
			t.Fatalf("Bad: %v", nodes[0])
		}
	})

	t.Run("get peered node", func(t *testing.T) {
		var out structs.IndexedNodeDump
		req := structs.NodeSpecificRequest{
			Datacenter: "dc1",
			Node:       "foo",
			PeerName:   "peer1",
		}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.NodeInfo", &req, &out))

		nodes := out.Dump
		require.Equal(t, 1, len(nodes))
		require.Equal(t, "foo", nodes[0].Node)
		require.Equal(t, "peer1", nodes[0].PeerName)
	})
}

func TestInternal_NodeDump(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	_, s1 := testServerWithConfig(t, func(config *Config) {
		config.PeeringTestAllowPeerRegistrations = true
	})
	codec := rpcClient(t, s1)

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	args := []*structs.RegisterRequest{
		{
			Datacenter: "dc1",
			Node:       "foo",
			Address:    "127.0.0.1",
			Service: &structs.NodeService{
				ID:      "db",
				Service: "db",
				Tags:    []string{"primary"},
			},
			Check: &structs.HealthCheck{
				Name:      "db connect",
				Status:    api.HealthPassing,
				ServiceID: "db",
			},
		},
		{
			Datacenter: "dc1",
			Node:       "bar",
			Address:    "127.0.0.2",
			Service: &structs.NodeService{
				ID:      "db",
				Service: "db",
				Tags:    []string{"replica"},
			},
			Check: &structs.HealthCheck{
				Name:      "db connect",
				Status:    api.HealthWarning,
				ServiceID: "db",
			},
		},
		{
			Datacenter: "dc1",
			Node:       "foo-peer",
			Address:    "127.0.0.3",
			PeerName:   "peer1",
		},
	}

	for _, reg := range args {
		err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", reg, nil)
		require.NoError(t, err)
	}

	err := s1.fsm.State().PeeringWrite(1, &pbpeering.PeeringWriteRequest{
		Peering: &pbpeering.Peering{
			ID:   "9e650110-ac74-4c5a-a6a8-9348b2bed4e9",
			Name: "peer1",
		},
	})
	require.NoError(t, err)

	var out2 structs.IndexedNodeDump
	req := structs.DCSpecificRequest{
		Datacenter: "dc1",
	}
	if err := msgpackrpc.CallWithCodec(codec, "Internal.NodeDump", &req, &out2); err != nil {
		t.Fatalf("err: %v", err)
	}

	nodes := out2.Dump
	if len(nodes) != 3 {
		t.Fatalf("Bad: %v", nodes)
	}

	var foundFoo, foundBar bool
	for _, node := range nodes {
		switch node.Node {
		case "foo":
			foundFoo = true
			if !stringslice.Contains(node.Services[0].Tags, "primary") {
				t.Fatalf("Bad: %v", nodes[0])
			}
			if node.Checks[0].Status != api.HealthPassing {
				t.Fatalf("Bad: %v", nodes[0])
			}

		case "bar":
			foundBar = true
			if !stringslice.Contains(node.Services[0].Tags, "replica") {
				t.Fatalf("Bad: %v", nodes[1])
			}
			if node.Checks[0].Status != api.HealthWarning {
				t.Fatalf("Bad: %v", nodes[1])
			}

		default:
			continue
		}
	}
	if !foundFoo || !foundBar {
		t.Fatalf("missing foo or bar")
	}

	require.Len(t, out2.ImportedDump, 1)
	require.Equal(t, "peer1", out2.ImportedDump[0].PeerName)
	require.Equal(t, "foo-peer", out2.ImportedDump[0].Node)
}

func TestInternal_NodeDump_Filter(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	_, s1 := testServerWithConfig(t, func(config *Config) {
		config.PeeringTestAllowPeerRegistrations = true
	})
	codec := rpcClient(t, s1)

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	args := []*structs.RegisterRequest{
		{
			Datacenter: "dc1",
			Node:       "foo",
			Address:    "127.0.0.1",
			Service: &structs.NodeService{
				ID:      "db",
				Service: "db",
				Tags:    []string{"primary"},
			},
			Check: &structs.HealthCheck{
				Name:      "db connect",
				Status:    api.HealthPassing,
				ServiceID: "db",
			},
		},
		{
			Datacenter: "dc1",
			Node:       "bar",
			Address:    "127.0.0.2",
			Service: &structs.NodeService{
				ID:      "db",
				Service: "db",
				Tags:    []string{"replica"},
			},
			Check: &structs.HealthCheck{
				Name:      "db connect",
				Status:    api.HealthWarning,
				ServiceID: "db",
			},
		},
		{
			Datacenter: "dc1",
			Node:       "foo-peer",
			Address:    "127.0.0.3",
			PeerName:   "peer1",
		},
	}

	for _, reg := range args {
		err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", reg, nil)
		require.NoError(t, err)
	}

	err := s1.fsm.State().PeeringWrite(1, &pbpeering.PeeringWriteRequest{
		Peering: &pbpeering.Peering{
			ID:   "9e650110-ac74-4c5a-a6a8-9348b2bed4e9",
			Name: "peer1",
		},
	})
	require.NoError(t, err)

	t.Run("filter on the local node", func(t *testing.T) {
		var out2 structs.IndexedNodeDump
		req := structs.DCSpecificRequest{
			Datacenter:   "dc1",
			QueryOptions: structs.QueryOptions{Filter: "primary in Services.Tags"},
		}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.NodeDump", &req, &out2))

		nodes := out2.Dump
		require.Len(t, nodes, 1)
		require.Equal(t, "foo", nodes[0].Node)
	})

	t.Run("filter on imported dump", func(t *testing.T) {
		var out3 structs.IndexedNodeDump
		req2 := structs.DCSpecificRequest{
			Datacenter:   "dc1",
			QueryOptions: structs.QueryOptions{Filter: "friend in PeerName"},
		}

		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.NodeDump", &req2, &out3))
		require.Len(t, out3.Dump, 0)
		require.Len(t, out3.ImportedDump, 0)
	})

	t.Run("filter look for peer nodes (non local nodes)", func(t *testing.T) {
		var out3 structs.IndexedNodeDump
		req2 := structs.DCSpecificRequest{
			QueryOptions: structs.QueryOptions{Filter: "PeerName != \"\""},
		}

		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.NodeDump", &req2, &out3))
		require.Len(t, out3.Dump, 0)
		require.Len(t, out3.ImportedDump, 1)
	})

	t.Run("filter look for a specific peer", func(t *testing.T) {
		var out3 structs.IndexedNodeDump
		req2 := structs.DCSpecificRequest{
			QueryOptions: structs.QueryOptions{Filter: "PeerName == peer1"},
		}

		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.NodeDump", &req2, &out3))
		require.Len(t, out3.Dump, 0)
		require.Len(t, out3.ImportedDump, 1)
	})
}

func TestInternal_KeyringOperation(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	key1 := "H1dfkSZOVnP/JUnaBfTzXg=="
	keyBytes1, err := base64.StdEncoding.DecodeString(key1)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	dir1, s1 := testServerWithConfig(t, func(c *Config) {
		c.SerfLANConfig.MemberlistConfig.SecretKey = keyBytes1
		c.SerfWANConfig.MemberlistConfig.SecretKey = keyBytes1
	})
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()
	codec := rpcClient(t, s1)
	defer codec.Close()

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	var out structs.KeyringResponses
	req := structs.KeyringRequest{
		Operation:  structs.KeyringList,
		Datacenter: "dc1",
	}
	if err := msgpackrpc.CallWithCodec(codec, "Internal.KeyringOperation", &req, &out); err != nil {
		t.Fatalf("err: %v", err)
	}

	// Two responses (local lan/wan pools) from single-node cluster
	if len(out.Responses) != 2 {
		t.Fatalf("bad: %#v", out)
	}
	if _, ok := out.Responses[0].Keys[key1]; !ok {
		t.Fatalf("bad: %#v", out)
	}
	wanResp, lanResp := 0, 0
	for _, resp := range out.Responses {
		if resp.WAN {
			wanResp++
		} else {
			lanResp++
		}
	}
	if lanResp != 1 || wanResp != 1 {
		t.Fatalf("should have one lan and one wan response")
	}

	// Start a second agent to test cross-dc queries
	dir2, s2 := testServerWithConfig(t, func(c *Config) {
		c.SerfLANConfig.MemberlistConfig.SecretKey = keyBytes1
		c.SerfWANConfig.MemberlistConfig.SecretKey = keyBytes1
		c.Datacenter = "dc2"
	})
	defer os.RemoveAll(dir2)
	defer s2.Shutdown()

	// Try to join
	joinWAN(t, s2, s1)

	var out2 structs.KeyringResponses
	req2 := structs.KeyringRequest{
		Operation: structs.KeyringList,
	}
	if err := msgpackrpc.CallWithCodec(codec, "Internal.KeyringOperation", &req2, &out2); err != nil {
		t.Fatalf("err: %v", err)
	}

	// 3 responses (one from each DC LAN, one from WAN) in two-node cluster
	if len(out2.Responses) != 3 {
		t.Fatalf("bad: %#v", out2)
	}
	wanResp, lanResp = 0, 0
	for _, resp := range out2.Responses {
		if resp.WAN {
			wanResp++
		} else {
			lanResp++
		}
	}
	if lanResp != 2 || wanResp != 1 {
		t.Fatalf("should have two lan and one wan response")
	}
}

func TestInternal_KeyringOperationList_LocalOnly(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	key1 := "H1dfkSZOVnP/JUnaBfTzXg=="
	keyBytes1, err := base64.StdEncoding.DecodeString(key1)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	dir1, s1 := testServerWithConfig(t, func(c *Config) {
		c.SerfLANConfig.MemberlistConfig.SecretKey = keyBytes1
		c.SerfWANConfig.MemberlistConfig.SecretKey = keyBytes1
	})
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()
	codec := rpcClient(t, s1)
	defer codec.Close()

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	// Start a second agent to test cross-dc queries
	dir2, s2 := testServerWithConfig(t, func(c *Config) {
		c.SerfLANConfig.MemberlistConfig.SecretKey = keyBytes1
		c.SerfWANConfig.MemberlistConfig.SecretKey = keyBytes1
		c.Datacenter = "dc2"
	})
	defer os.RemoveAll(dir2)
	defer s2.Shutdown()

	// Try to join
	joinWAN(t, s2, s1)

	// --
	// Try request with `LocalOnly` set to true
	var out structs.KeyringResponses
	req := structs.KeyringRequest{
		Operation: structs.KeyringList,
		LocalOnly: true,
	}
	if err := msgpackrpc.CallWithCodec(codec, "Internal.KeyringOperation", &req, &out); err != nil {
		t.Fatalf("err: %v", err)
	}

	// 1 response (from this DC LAN)
	if len(out.Responses) != 1 {
		t.Fatalf("expected num responses to be 1, got %d; out is: %#v", len(out.Responses), out)
	}
	wanResp, lanResp := 0, 0
	for _, resp := range out.Responses {
		if resp.WAN {
			wanResp++
		} else {
			lanResp++
		}
	}
	if lanResp != 1 || wanResp != 0 {
		t.Fatalf("should have 1 lan and 0 wan response, got (lan=%d) (wan=%d)", lanResp, wanResp)
	}

	// --
	// Try same request again but with `LocalOnly` set to false
	req.LocalOnly = false
	if err := msgpackrpc.CallWithCodec(codec, "Internal.KeyringOperation", &req, &out); err != nil {
		t.Fatalf("err: %v", err)
	}

	// 3 responses (one from each DC LAN, one from WAN)
	if len(out.Responses) != 3 {
		t.Fatalf("expected num responses to be 3, got %d; out is: %#v", len(out.Responses), out)
	}
	wanResp, lanResp = 0, 0
	for _, resp := range out.Responses {
		if resp.WAN {
			wanResp++
		} else {
			lanResp++
		}
	}
	if lanResp != 2 || wanResp != 1 {
		t.Fatalf("should have 2 lan and 1 wan response, got (lan=%d) (wan=%d)", lanResp, wanResp)
	}
}

func TestInternal_KeyringOperationWrite_LocalOnly(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	key1 := "H1dfkSZOVnP/JUnaBfTzXg=="
	keyBytes1, err := base64.StdEncoding.DecodeString(key1)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	dir1, s1 := testServerWithConfig(t, func(c *Config) {
		c.SerfLANConfig.MemberlistConfig.SecretKey = keyBytes1
		c.SerfWANConfig.MemberlistConfig.SecretKey = keyBytes1
	})
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()
	codec := rpcClient(t, s1)
	defer codec.Close()

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	// Try request with `LocalOnly` set to true
	var out structs.KeyringResponses
	req := structs.KeyringRequest{
		Operation: structs.KeyringRemove,
		LocalOnly: true,
	}
	err = msgpackrpc.CallWithCodec(codec, "Internal.KeyringOperation", &req, &out)
	if err == nil {
		t.Fatalf("expected an error")
	}
	if !strings.Contains(err.Error(), "LocalOnly") {
		t.Fatalf("expected error to contain string 'LocalOnly'. Got: %v", err)
	}
}

func TestInternal_NodeInfo_FilterACL(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	dir, token, srv, codec := testACLFilterServer(t)
	defer os.RemoveAll(dir)
	defer srv.Shutdown()
	defer codec.Close()

	opt := structs.NodeSpecificRequest{
		Datacenter:   "dc1",
		Node:         srv.config.NodeName,
		QueryOptions: structs.QueryOptions{Token: token},
	}
	reply := structs.IndexedNodeDump{}
	if err := msgpackrpc.CallWithCodec(codec, "Internal.NodeInfo", &opt, &reply); err != nil {
		t.Fatalf("err: %s", err)
	}
	for _, info := range reply.Dump {
		found := false
		for _, chk := range info.Checks {
			if chk.ServiceName == "foo" {
				found = true
			}
			if chk.ServiceName == "bar" {
				t.Fatalf("bad: %#v", info.Checks)
			}
		}
		if !found {
			t.Fatalf("bad: %#v", info.Checks)
		}

		found = false
		for _, svc := range info.Services {
			if svc.Service == "foo" {
				found = true
			}
			if svc.Service == "bar" {
				t.Fatalf("bad: %#v", info.Services)
			}
		}
		if !found {
			t.Fatalf("bad: %#v", info.Services)
		}
	}

	if !reply.ResultsFilteredByACLs {
		t.Fatal("ResultsFilteredByACLs should be true")
	}

	// We've already proven that we call the ACL filtering function so we
	// test node filtering down in acl.go for node cases. This also proves
	// that we respect the version 8 ACL flag, since the test server sets
	// that to false (the regression value of *not* changing this is better
	// for now until we change the sense of the version 8 ACL flag).
}

func TestInternal_NodeDump_FilterACL(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	dir, token, srv, codec := testACLFilterServer(t)
	defer os.RemoveAll(dir)
	defer srv.Shutdown()
	defer codec.Close()

	opt := structs.DCSpecificRequest{
		Datacenter:   "dc1",
		QueryOptions: structs.QueryOptions{Token: token},
	}
	reply := structs.IndexedNodeDump{}
	if err := msgpackrpc.CallWithCodec(codec, "Internal.NodeDump", &opt, &reply); err != nil {
		t.Fatalf("err: %s", err)
	}
	for _, info := range reply.Dump {
		found := false
		for _, chk := range info.Checks {
			if chk.ServiceName == "foo" {
				found = true
			}
			if chk.ServiceName == "bar" {
				t.Fatalf("bad: %#v", info.Checks)
			}
		}
		if !found {
			t.Fatalf("bad: %#v", info.Checks)
		}

		found = false
		for _, svc := range info.Services {
			if svc.Service == "foo" {
				found = true
			}
			if svc.Service == "bar" {
				t.Fatalf("bad: %#v", info.Services)
			}
		}
		if !found {
			t.Fatalf("bad: %#v", info.Services)
		}
	}

	if !reply.ResultsFilteredByACLs {
		t.Fatal("ResultsFilteredByACLs should be true")
	}

	// need to ensure that ACLs are filtered prior to bexprFiltering
	// Register additional node
	regArgs := &structs.RegisterRequest{
		Datacenter: "dc1",
		Node:       "foo",
		Address:    "127.0.0.1",
		WriteRequest: structs.WriteRequest{
			Token: "root",
		},
	}

	var out struct{}
	require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", regArgs, &out))

	bexprMatchingUserTokenPermissions := fmt.Sprintf("Node matches `%s.*`", srv.config.NodeName)
	const bexpNotMatchingUserTokenPermissions = "Node matches `node-deny.*`"

	t.Run("request with filter that matches token permissions returns 1 result and ResultsFilteredByACLs equal to true", func(t *testing.T) {
		req := structs.DCSpecificRequest{
			Datacenter: "dc1",
			QueryOptions: structs.QueryOptions{
				Token:  token,
				Filter: bexprMatchingUserTokenPermissions,
			},
		}

		reply = structs.IndexedNodeDump{}
		if err := msgpackrpc.CallWithCodec(codec, "Internal.NodeDump", &req, &reply); err != nil {
			t.Fatalf("err: %s", err)
		}
		require.Equal(t, 1, len(reply.Dump))
		require.True(t, reply.ResultsFilteredByACLs)
	})

	t.Run("request with filter that does not match token permissions returns 0 results and ResultsFilteredByACLs equal to true", func(t *testing.T) {
		req := structs.DCSpecificRequest{
			Datacenter: "dc1",
			QueryOptions: structs.QueryOptions{
				Token:  token,
				Filter: bexpNotMatchingUserTokenPermissions,
			},
		}

		reply = structs.IndexedNodeDump{}
		if err := msgpackrpc.CallWithCodec(codec, "Internal.NodeDump", &req, &reply); err != nil {
			t.Fatalf("err: %s", err)
		}
		require.Zero(t, len(reply.Dump))
		require.True(t, reply.ResultsFilteredByACLs)
	})

	t.Run("request with filter that would match only record without any token returns zero results and ResultsFilteredByACLs equal to false", func(t *testing.T) {
		req := structs.DCSpecificRequest{
			Datacenter: "dc1",
			QueryOptions: structs.QueryOptions{
				Token:  "",
				Filter: bexprMatchingUserTokenPermissions,
			},
		}

		reply = structs.IndexedNodeDump{}
		if err := msgpackrpc.CallWithCodec(codec, "Internal.NodeDump", &req, &reply); err != nil {
			t.Fatalf("err: %s", err)
		}
		require.Empty(t, reply.Dump)
		require.False(t, reply.ResultsFilteredByACLs)
	})
}

func TestInternal_EventFire_Token(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	dir, srv := testServerWithConfig(t, func(c *Config) {
		c.PrimaryDatacenter = "dc1"
		c.ACLsEnabled = true
		c.ACLInitialManagementToken = "root"
		c.ACLResolverSettings.ACLDownPolicy = "deny"
		c.ACLResolverSettings.ACLDefaultPolicy = "deny"
	})
	defer os.RemoveAll(dir)
	defer srv.Shutdown()

	codec := rpcClient(t, srv)
	defer codec.Close()

	testrpc.WaitForLeader(t, srv.RPC, "dc1")

	// No token is rejected
	event := structs.EventFireRequest{
		Name:       "foo",
		Datacenter: "dc1",
		Payload:    []byte("nope"),
	}
	err := msgpackrpc.CallWithCodec(codec, "Internal.EventFire", &event, nil)
	if !acl.IsErrPermissionDenied(err) {
		t.Fatalf("bad: %s", err)
	}

	// Root token is allowed to fire
	event.Token = "root"
	err = msgpackrpc.CallWithCodec(codec, "Internal.EventFire", &event, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
}

func TestInternal_ServiceDump(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()
	codec := rpcClient(t, s1)
	defer codec.Close()

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	// prep the cluster with some data we can use in our filters
	registerTestCatalogEntries(t, codec)

	// Register a gateway config entry to ensure gateway-services is dumped
	{
		req := structs.ConfigEntryRequest{
			Op:         structs.ConfigEntryUpsert,
			Datacenter: "dc1",
			Entry: &structs.TerminatingGatewayConfigEntry{
				Name: "terminating-gateway",
				Kind: structs.TerminatingGateway,
				Services: []structs.LinkedService{
					{
						Name: "api",
					},
					{
						Name: "cache",
					},
				},
			},
		}
		var configOutput bool
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "ConfigEntry.Apply", &req, &configOutput))
		require.True(t, configOutput)
	}

	doRequest := func(t *testing.T, filter string) structs.IndexedNodesWithGateways {
		t.Helper()
		args := structs.DCSpecificRequest{
			Datacenter:   "dc1",
			QueryOptions: structs.QueryOptions{Filter: filter},
		}

		var out structs.IndexedNodesWithGateways
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &args, &out))

		// The GatewayServices dump is currently cannot be bexpr filtered
		// so the response should be the same in all subtests
		expectedGW := structs.GatewayServices{
			{
				Service:     structs.NewServiceName("api", nil),
				Gateway:     structs.NewServiceName("terminating-gateway", nil),
				GatewayKind: structs.ServiceKindTerminatingGateway,
			},
			{
				Service:     structs.NewServiceName("cache", nil),
				Gateway:     structs.NewServiceName("terminating-gateway", nil),
				GatewayKind: structs.ServiceKindTerminatingGateway,
			},
		}
		assert.Len(t, out.Gateways, 2)
		assert.Equal(t, expectedGW[0].Service, out.Gateways[0].Service)
		assert.Equal(t, expectedGW[0].Gateway, out.Gateways[0].Gateway)
		assert.Equal(t, expectedGW[0].GatewayKind, out.Gateways[0].GatewayKind)

		assert.Equal(t, expectedGW[1].Service, out.Gateways[1].Service)
		assert.Equal(t, expectedGW[1].Gateway, out.Gateways[1].Gateway)
		assert.Equal(t, expectedGW[1].GatewayKind, out.Gateways[1].GatewayKind)

		return out
	}

	// Run the tests against the test server
	t.Run("No Filter", func(t *testing.T) {
		nodes := doRequest(t, "")
		// redis (3), web (3), critical (1), warning (1) and consul (1)
		require.Len(t, nodes.Nodes, 9)

	})

	t.Run("Filter Node foo and service version 1", func(t *testing.T) {
		resp := doRequest(t, "Node.Node == foo and Service.Meta.version == 1")
		require.Len(t, resp.Nodes, 1)
		require.Equal(t, "redis", resp.Nodes[0].Service.Service)
		require.Equal(t, "redisV1", resp.Nodes[0].Service.ID)
	})

	t.Run("Filter service web", func(t *testing.T) {
		resp := doRequest(t, "Service.Service == web")
		require.Len(t, resp.Nodes, 3)
	})
}

func TestInternal_ServiceDump_Kind(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()
	codec := rpcClient(t, s1)
	defer codec.Close()

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	// prep the cluster with some data we can use in our filters
	registerTestCatalogEntries(t, codec)
	registerTestCatalogProxyEntries(t, codec)

	doRequest := func(t *testing.T, kind structs.ServiceKind) structs.CheckServiceNodes {
		t.Helper()
		args := structs.ServiceDumpRequest{
			Datacenter:     "dc1",
			ServiceKind:    kind,
			UseServiceKind: true,
		}

		var out structs.IndexedNodesWithGateways
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &args, &out))
		return out.Nodes
	}

	// Run the tests against the test server
	t.Run("Typical", func(t *testing.T) {
		nodes := doRequest(t, structs.ServiceKindTypical)
		// redis (3), web (3), critical (1), warning (1) and consul (1)
		require.Len(t, nodes, 9)
	})

	t.Run("Terminating Gateway", func(t *testing.T) {
		nodes := doRequest(t, structs.ServiceKindTerminatingGateway)
		require.Len(t, nodes, 1)
		require.Equal(t, "tg-gw", nodes[0].Service.Service)
		require.Equal(t, "tg-gw-01", nodes[0].Service.ID)
	})

	t.Run("Mesh Gateway", func(t *testing.T) {
		nodes := doRequest(t, structs.ServiceKindMeshGateway)
		require.Len(t, nodes, 1)
		require.Equal(t, "mg-gw", nodes[0].Service.Service)
		require.Equal(t, "mg-gw-01", nodes[0].Service.ID)
	})

	t.Run("Connect Proxy", func(t *testing.T) {
		nodes := doRequest(t, structs.ServiceKindConnectProxy)
		require.Len(t, nodes, 1)
		require.Equal(t, "web-proxy", nodes[0].Service.Service)
		require.Equal(t, "web-proxy", nodes[0].Service.ID)
	})
}

func TestInternal_ServiceDump_ACL(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()

	dir, s := testServerWithConfig(t, func(c *Config) {
		c.PrimaryDatacenter = "dc1"
		c.ACLsEnabled = true
		c.ACLInitialManagementToken = "root"
		c.ACLResolverSettings.ACLDefaultPolicy = "deny"
	})
	defer os.RemoveAll(dir)
	defer s.Shutdown()
	codec := rpcClient(t, s)
	defer codec.Close()

	testrpc.WaitForLeader(t, s.RPC, "dc1")

	registrations := []*structs.RegisterRequest{
		// Service `redis` on `node1`
		{
			Datacenter: "dc1",
			Node:       "node1",
			ID:         types.NodeID("e0155642-135d-4739-9853-a1ee6c9f945b"),
			Address:    "192.18.1.1",
			Service: &structs.NodeService{
				Kind:    structs.ServiceKindTypical,
				ID:      "redis",
				Service: "redis",
				Port:    5678,
			},
			Check: &structs.HealthCheck{
				Name:      "redis check",
				Status:    api.HealthPassing,
				ServiceID: "redis",
			},
		},
		// Ingress gateway `igw` on `node2`
		{
			Datacenter: "dc1",
			Node:       "node2",
			ID:         types.NodeID("3a9d7530-20d4-443a-98d3-c10fe78f09f4"),
			Address:    "192.18.1.2",
			Service: &structs.NodeService{
				Kind:    structs.ServiceKindIngressGateway,
				ID:      "igw",
				Service: "igw",
			},
			Check: &structs.HealthCheck{
				Name:      "igw check",
				Status:    api.HealthPassing,
				ServiceID: "igw",
			},
		},
	}
	for _, reg := range registrations {
		reg.Token = "root"
		err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", reg, nil)
		require.NoError(t, err)
	}

	{
		req := structs.ConfigEntryRequest{
			Datacenter: "dc1",
			Entry: &structs.IngressGatewayConfigEntry{
				Kind: structs.IngressGateway,
				Name: "igw",
				Listeners: []structs.IngressListener{
					{
						Port:     8765,
						Protocol: "tcp",
						Services: []structs.IngressService{
							{Name: "redis"},
						},
					},
				},
			},
		}
		req.Token = "root"

		var out bool
		err := msgpackrpc.CallWithCodec(codec, "ConfigEntry.Apply", &req, &out)
		require.NoError(t, err)
	}

	tokenWithRules := func(t *testing.T, rules string) string {
		t.Helper()
		tok, err := upsertTestTokenWithPolicyRules(codec, "root", "dc1", rules)
		require.NoError(t, err)
		return tok.SecretID
	}

	t.Run("can read all", func(t *testing.T) {

		token := tokenWithRules(t, `
			node_prefix "" {
				policy = "read"
			}
			service_prefix "" {
				policy = "read"
			}
		`)

		args := structs.DCSpecificRequest{
			Datacenter:   "dc1",
			QueryOptions: structs.QueryOptions{Token: token},
		}
		var out structs.IndexedNodesWithGateways
		err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &args, &out)
		require.NoError(t, err)
		require.NotEmpty(t, out.Nodes)
		require.NotEmpty(t, out.Gateways)
		require.False(t, out.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
	})

	t.Run("cannot read service node", func(t *testing.T) {

		token := tokenWithRules(t, `
			node "node1" {
				policy = "deny"
			}
			service "redis" {
				policy = "read"
			}
		`)

		args := structs.DCSpecificRequest{
			Datacenter:   "dc1",
			QueryOptions: structs.QueryOptions{Token: token},
		}
		var out structs.IndexedNodesWithGateways
		err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &args, &out)
		require.NoError(t, err)
		require.Empty(t, out.Nodes)
		require.True(t, out.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
	})

	t.Run("cannot read service", func(t *testing.T) {

		token := tokenWithRules(t, `
			node "node1" {
				policy = "read"
			}
			service "redis" {
				policy = "deny"
			}
		`)

		args := structs.DCSpecificRequest{
			Datacenter:   "dc1",
			QueryOptions: structs.QueryOptions{Token: token},
		}
		var out structs.IndexedNodesWithGateways
		err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &args, &out)
		require.NoError(t, err)
		require.Empty(t, out.Nodes)
		require.True(t, out.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
	})

	t.Run("cannot read gateway node", func(t *testing.T) {

		token := tokenWithRules(t, `
			node "node2" {
				policy = "deny"
			}
			service "mgw" {
				policy = "read"
			}
		`)

		args := structs.DCSpecificRequest{
			Datacenter:   "dc1",
			QueryOptions: structs.QueryOptions{Token: token},
		}
		var out structs.IndexedNodesWithGateways
		err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &args, &out)
		require.NoError(t, err)
		require.Empty(t, out.Gateways)
		require.True(t, out.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
	})

	t.Run("cannot read gateway", func(t *testing.T) {

		token := tokenWithRules(t, `
			node "node2" {
				policy = "read"
			}
			service "mgw" {
				policy = "deny"
			}
		`)

		args := structs.DCSpecificRequest{
			Datacenter:   "dc1",
			QueryOptions: structs.QueryOptions{Token: token},
		}
		var out structs.IndexedNodesWithGateways
		err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &args, &out)
		require.NoError(t, err)
		require.Empty(t, out.Gateways)
		require.True(t, out.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
	})

	// need to ensure that ACLs are filtered prior to bexprFiltering
	// Register additional node
	regArgs := &structs.RegisterRequest{
		Datacenter: "dc1",
		Node:       "node-deny",
		ID:         types.NodeID("e0155642-135d-4739-9853-b1ee6c9f945b"),
		Address:    "192.18.1.2",
		Service: &structs.NodeService{
			Kind:    structs.ServiceKindTypical,
			ID:      "memcached",
			Service: "memcached",
			Port:    5678,
		},
		Check: &structs.HealthCheck{
			Name:      "memcached check",
			Status:    api.HealthPassing,
			ServiceID: "memcached",
		},
		WriteRequest: structs.WriteRequest{
			Token: "root",
		},
	}

	var out struct{}
	require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", regArgs, &out))

	const (
		bexprMatchingUserTokenPermissions   = "Service.Service matches `redis.*`"
		bexpNotMatchingUserTokenPermissions = "Node.Node matches `node-deny.*`"
	)

	t.Run("request with filter that matches token permissions returns 1 result and ResultsFilteredByACLs equal to true", func(t *testing.T) {
		token := tokenWithRules(t, `
			node "node-deny" {
				policy = "deny"
			}
			node "node1" {
				policy = "read"
			}
			service "redis" {
				policy = "read"
			}
		`)
		var reply structs.IndexedNodesWithGateways
		req := structs.DCSpecificRequest{
			Datacenter: "dc1",
			QueryOptions: structs.QueryOptions{
				Token:  token,
				Filter: bexprMatchingUserTokenPermissions,
			},
		}

		reply = structs.IndexedNodesWithGateways{}
		if err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &req, &reply); err != nil {
			t.Fatalf("err: %s", err)
		}
		require.Equal(t, 1, len(reply.Nodes))
		require.True(t, reply.ResultsFilteredByACLs)
	})

	t.Run("request with filter that does not match token permissions returns 0 results and ResultsFilteredByACLs equal to true", func(t *testing.T) {
		token := tokenWithRules(t, `
			node "node-deny" {
				policy = "deny"
			}
			node "node1" {
				policy = "read"
			}
			service "redis" {
				policy = "read"
			}
		`)
		var reply structs.IndexedNodesWithGateways
		req := structs.DCSpecificRequest{
			Datacenter: "dc1",
			QueryOptions: structs.QueryOptions{
				Token:  token,
				Filter: bexpNotMatchingUserTokenPermissions,
			},
		}

		reply = structs.IndexedNodesWithGateways{}
		if err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &req, &reply); err != nil {
			t.Fatalf("err: %s", err)
		}
		require.Zero(t, len(reply.Nodes))
		require.True(t, reply.ResultsFilteredByACLs)
	})

	t.Run("request with filter that would match only record without any token returns zero results and ResultsFilteredByACLs equal to false", func(t *testing.T) {
		var reply structs.IndexedNodesWithGateways
		req := structs.DCSpecificRequest{
			Datacenter: "dc1",
			QueryOptions: structs.QueryOptions{
				Token:  "", // no token
				Filter: bexpNotMatchingUserTokenPermissions,
			},
		}

		reply = structs.IndexedNodesWithGateways{}
		if err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &req, &reply); err != nil {
			t.Fatalf("err: %s", err)
		}
		require.Empty(t, reply.Nodes)
		require.False(t, reply.ResultsFilteredByACLs)
	})
}

func TestInternal_GatewayServiceDump_Terminating(t *testing.T) {
	netutil.GetAgentBindAddrFunc = netutil.GetMockGetAgentBindAddrFunc("0.0.0.0")
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()
	codec := rpcClient(t, s1)
	defer codec.Close()

	testrpc.WaitForTestAgent(t, s1.RPC, "dc1")

	// Register gateway and two service instances that will be associated with it
	{
		arg := structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "foo",
			Address:    "127.0.0.1",
			Service: &structs.NodeService{
				ID:      "terminating-gateway",
				Service: "terminating-gateway",
				Kind:    structs.ServiceKindTerminatingGateway,
				Port:    443,
			},
			Check: &structs.HealthCheck{
				Name:      "terminating connect",
				Status:    api.HealthPassing,
				ServiceID: "terminating-gateway",
			},
		}
		var out struct{}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out))

		arg = structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "bar",
			Address:    "127.0.0.2",
			Service: &structs.NodeService{
				ID:      "db",
				Service: "db",
			},
			Check: &structs.HealthCheck{
				Name:      "db-warning",
				Status:    api.HealthWarning,
				ServiceID: "db",
			},
		}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out))

		arg = structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "baz",
			Address:    "127.0.0.3",
			Service: &structs.NodeService{
				ID:      "db2",
				Service: "db",
			},
			Check: &structs.HealthCheck{
				Name:      "db2-passing",
				Status:    api.HealthPassing,
				ServiceID: "db2",
			},
		}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out))
	}

	// Register terminating-gateway config entry, linking it to db, api, and redis (dne)
	{
		args := &structs.TerminatingGatewayConfigEntry{
			Name: "terminating-gateway",
			Kind: structs.TerminatingGateway,
			Services: []structs.LinkedService{
				{
					Name: "db",
				},
				{
					Name:     "redis",
					CAFile:   "/etc/certs/ca.pem",
					CertFile: "/etc/certs/cert.pem",
					KeyFile:  "/etc/certs/key.pem",
				},
			},
		}

		req := structs.ConfigEntryRequest{
			Op:         structs.ConfigEntryUpsert,
			Datacenter: "dc1",
			Entry:      args,
		}
		var configOutput bool
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "ConfigEntry.Apply", &req, &configOutput))
		require.True(t, configOutput)
	}

	var out structs.IndexedServiceDump
	req := structs.ServiceSpecificRequest{
		Datacenter:  "dc1",
		ServiceName: "terminating-gateway",
	}
	require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.GatewayServiceDump", &req, &out))

	dump := out.Dump

	// Reset raft indices to facilitate assertion
	for i := 0; i < len(dump); i++ {
		svc := dump[i]
		if svc.Node != nil {
			svc.Node.RaftIndex = structs.RaftIndex{}
		}
		if svc.Service != nil {
			svc.Service.RaftIndex = structs.RaftIndex{}
		}
		if len(svc.Checks) > 0 && svc.Checks[0] != nil {
			svc.Checks[0].RaftIndex = structs.RaftIndex{}
		}
		if svc.GatewayService != nil {
			svc.GatewayService.RaftIndex = structs.RaftIndex{}
		}
	}

	expect := structs.ServiceDump{
		{
			Node: &structs.Node{
				Node:       "baz",
				Partition:  structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
				Address:    "127.0.0.3",
				Datacenter: "dc1",
			},
			Service: &structs.NodeService{
				ID:      "db2",
				Service: "db",
				Weights: &structs.Weights{
					Passing: 1,
					Warning: 1,
				},
				EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
			},
			Checks: structs.HealthChecks{
				{
					Node:           "baz",
					CheckID:        types.CheckID("db2-passing"),
					Name:           "db2-passing",
					Status:         "passing",
					ServiceID:      "db2",
					ServiceName:    "db",
					EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
				},
			},
			GatewayService: &structs.GatewayService{
				Gateway:         structs.NewServiceName("terminating-gateway", nil),
				Service:         structs.NewServiceName("db", nil),
				GatewayKind:     "terminating-gateway",
				ServiceKind:     structs.GatewayServiceKindService,
				AutoHostRewrite: true,
			},
		},
		{
			Node: &structs.Node{
				Node:       "bar",
				Partition:  structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
				Address:    "127.0.0.2",
				Datacenter: "dc1",
			},
			Service: &structs.NodeService{
				ID:      "db",
				Service: "db",
				Weights: &structs.Weights{
					Passing: 1,
					Warning: 1,
				},
				EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
			},
			Checks: structs.HealthChecks{
				{
					Node:           "bar",
					CheckID:        types.CheckID("db-warning"),
					Name:           "db-warning",
					Status:         "warning",
					ServiceID:      "db",
					ServiceName:    "db",
					EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
				},
			},
			GatewayService: &structs.GatewayService{
				Gateway:         structs.NewServiceName("terminating-gateway", nil),
				Service:         structs.NewServiceName("db", nil),
				GatewayKind:     "terminating-gateway",
				ServiceKind:     structs.GatewayServiceKindService,
				AutoHostRewrite: true,
			},
		},
		{
			// Only GatewayService should be returned when linked service isn't registered
			GatewayService: &structs.GatewayService{
				Gateway:         structs.NewServiceName("terminating-gateway", nil),
				Service:         structs.NewServiceName("redis", nil),
				GatewayKind:     "terminating-gateway",
				CAFile:          "/etc/certs/ca.pem",
				CertFile:        "/etc/certs/cert.pem",
				KeyFile:         "/etc/certs/key.pem",
				AutoHostRewrite: true,
			},
		},
	}
	assert.ElementsMatch(t, expect, dump)
}

func TestInternal_GatewayServiceDump_Terminating_ACL(t *testing.T) {
	netutil.GetAgentBindAddrFunc = netutil.GetMockGetAgentBindAddrFunc("0.0.0.0")
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	dir1, s1 := testServerWithConfig(t, func(c *Config) {
		c.PrimaryDatacenter = "dc1"
		c.ACLsEnabled = true
		c.ACLInitialManagementToken = "root"
		c.ACLResolverSettings.ACLDefaultPolicy = "deny"
	})
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()
	codec := rpcClient(t, s1)
	defer codec.Close()

	testrpc.WaitForTestAgent(t, s1.RPC, "dc1", testrpc.WithToken("root"))

	// Create the ACL.
	token, err := upsertTestTokenWithPolicyRules(codec, "root", "dc1", `
  	service "db" { policy = "read" }
	service "terminating-gateway" { policy = "read" }
	node_prefix "" { policy = "read" }`)
	require.NoError(t, err)

	// Register gateway and two service instances that will be associated with it
	{
		arg := structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "foo",
			Address:    "127.0.0.1",
			Service: &structs.NodeService{
				ID:      "terminating-gateway",
				Service: "terminating-gateway",
				Kind:    structs.ServiceKindTerminatingGateway,
				Port:    443,
			},
			Check: &structs.HealthCheck{
				Name:      "terminating connect",
				Status:    api.HealthPassing,
				ServiceID: "terminating-gateway",
			},
			WriteRequest: structs.WriteRequest{Token: "root"},
		}
		var out struct{}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out))

		arg = structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "bar",
			Address:    "127.0.0.2",
			Service: &structs.NodeService{
				ID:      "db",
				Service: "db",
			},
			Check: &structs.HealthCheck{
				Name:      "db-warning",
				Status:    api.HealthWarning,
				ServiceID: "db",
			},
			WriteRequest: structs.WriteRequest{Token: "root"},
		}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out))

		arg = structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "baz",
			Address:    "127.0.0.3",
			Service: &structs.NodeService{
				ID:      "api",
				Service: "api",
			},
			Check: &structs.HealthCheck{
				Name:      "api-passing",
				Status:    api.HealthPassing,
				ServiceID: "api",
			},
			WriteRequest: structs.WriteRequest{Token: "root"},
		}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out))
	}

	// Register terminating-gateway config entry, linking it to db and api
	{
		args := &structs.TerminatingGatewayConfigEntry{
			Name: "terminating-gateway",
			Kind: structs.TerminatingGateway,
			Services: []structs.LinkedService{
				{Name: "db"},
				{Name: "api"},
			},
		}

		req := structs.ConfigEntryRequest{
			Op:           structs.ConfigEntryUpsert,
			Datacenter:   "dc1",
			Entry:        args,
			WriteRequest: structs.WriteRequest{Token: "root"},
		}
		var out bool
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "ConfigEntry.Apply", &req, &out))
		require.True(t, out)
	}

	var out structs.IndexedServiceDump

	// Not passing a token with service:read on Gateway leads to PermissionDenied
	req := structs.ServiceSpecificRequest{
		Datacenter:  "dc1",
		ServiceName: "terminating-gateway",
	}
	err = msgpackrpc.CallWithCodec(codec, "Internal.GatewayServiceDump", &req, &out)
	require.Error(t, err, acl.ErrPermissionDenied)

	// Passing a token without service:read on api leads to it getting filtered out
	req = structs.ServiceSpecificRequest{
		Datacenter:   "dc1",
		ServiceName:  "terminating-gateway",
		QueryOptions: structs.QueryOptions{Token: token.SecretID},
	}
	require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.GatewayServiceDump", &req, &out))

	nodes := out.Dump
	require.Len(t, nodes, 1)
	require.Equal(t, nodes[0].Node.Node, "bar")
	require.Equal(t, nodes[0].Service.Service, "db")
	require.Equal(t, nodes[0].Checks[0].Status, api.HealthWarning)
	require.True(t, out.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
}

func TestInternal_GatewayServiceDump_Ingress(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()
	codec := rpcClient(t, s1)
	defer codec.Close()

	testrpc.WaitForTestAgent(t, s1.RPC, "dc1")

	// Register gateway and service instance that will be associated with it
	{
		arg := structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "foo",
			Address:    "127.0.0.1",
			Service: &structs.NodeService{
				ID:      "ingress-gateway",
				Service: "ingress-gateway",
				Kind:    structs.ServiceKindIngressGateway,
				Port:    8443,
			},
			Check: &structs.HealthCheck{
				Name:      "ingress connect",
				Status:    api.HealthPassing,
				ServiceID: "ingress-gateway",
			},
		}
		var regOutput struct{}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &regOutput))

		arg = structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "bar",
			Address:    "127.0.0.2",
			Service: &structs.NodeService{
				ID:      "db",
				Service: "db",
			},
			Check: &structs.HealthCheck{
				Name:      "db-warning",
				Status:    api.HealthWarning,
				ServiceID: "db",
			},
		}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &regOutput))

		arg = structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "baz",
			Address:    "127.0.0.3",
			Service: &structs.NodeService{
				ID:      "db2",
				Service: "db",
			},
			Check: &structs.HealthCheck{
				Name:      "db2-passing",
				Status:    api.HealthPassing,
				ServiceID: "db2",
			},
		}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &regOutput))

		// Register ingress-gateway config entry, linking it to db and redis (dne)
		args := &structs.IngressGatewayConfigEntry{
			Name: "ingress-gateway",
			Kind: structs.IngressGateway,
			Listeners: []structs.IngressListener{
				{
					Port:     8888,
					Protocol: "tcp",
					Services: []structs.IngressService{
						{
							Name: "db",
						},
					},
				},
				{
					Port:     8080,
					Protocol: "tcp",
					Services: []structs.IngressService{
						{
							Name: "web",
						},
					},
				},
			},
		}

		req := structs.ConfigEntryRequest{
			Op:         structs.ConfigEntryUpsert,
			Datacenter: "dc1",
			Entry:      args,
		}
		var configOutput bool
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "ConfigEntry.Apply", &req, &configOutput))
		require.True(t, configOutput)
	}

	var out structs.IndexedServiceDump
	req := structs.ServiceSpecificRequest{
		Datacenter:  "dc1",
		ServiceName: "ingress-gateway",
	}
	require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.GatewayServiceDump", &req, &out))

	dump := out.Dump

	// Reset raft indices to facilitate assertion
	for i := 0; i < len(dump); i++ {
		svc := dump[i]
		if svc.Node != nil {
			svc.Node.RaftIndex = structs.RaftIndex{}
		}
		if svc.Service != nil {
			svc.Service.RaftIndex = structs.RaftIndex{}
		}
		if len(svc.Checks) > 0 && svc.Checks[0] != nil {
			svc.Checks[0].RaftIndex = structs.RaftIndex{}
		}
		if svc.GatewayService != nil {
			svc.GatewayService.RaftIndex = structs.RaftIndex{}
		}
	}

	expect := structs.ServiceDump{
		{
			Node: &structs.Node{
				Node:       "bar",
				Partition:  structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
				Address:    "127.0.0.2",
				Datacenter: "dc1",
			},
			Service: &structs.NodeService{
				Kind:    "",
				ID:      "db",
				Service: "db",
				Weights: &structs.Weights{
					Passing: 1,
					Warning: 1,
				},
				EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
			},
			Checks: structs.HealthChecks{
				{
					Node:           "bar",
					CheckID:        types.CheckID("db-warning"),
					Name:           "db-warning",
					Status:         "warning",
					ServiceID:      "db",
					ServiceName:    "db",
					EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
				},
			},
			GatewayService: &structs.GatewayService{
				Gateway:     structs.NewServiceName("ingress-gateway", nil),
				Service:     structs.NewServiceName("db", nil),
				GatewayKind: "ingress-gateway",
				Port:        8888,
				Protocol:    "tcp",
			},
		},
		{
			Node: &structs.Node{
				Node:       "baz",
				Partition:  structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
				Address:    "127.0.0.3",
				Datacenter: "dc1",
			},
			Service: &structs.NodeService{
				ID:      "db2",
				Service: "db",
				Weights: &structs.Weights{
					Passing: 1,
					Warning: 1,
				},
				EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
			},
			Checks: structs.HealthChecks{
				{
					Node:           "baz",
					CheckID:        types.CheckID("db2-passing"),
					Name:           "db2-passing",
					Status:         "passing",
					ServiceID:      "db2",
					ServiceName:    "db",
					EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
				},
			},
			GatewayService: &structs.GatewayService{
				Gateway:     structs.NewServiceName("ingress-gateway", nil),
				Service:     structs.NewServiceName("db", nil),
				GatewayKind: "ingress-gateway",
				Port:        8888,
				Protocol:    "tcp",
			},
		},
		{
			// Only GatewayService should be returned when upstream isn't registered
			GatewayService: &structs.GatewayService{
				Gateway:     structs.NewServiceName("ingress-gateway", nil),
				Service:     structs.NewServiceName("web", nil),
				GatewayKind: "ingress-gateway",
				Port:        8080,
				Protocol:    "tcp",
			},
		},
	}
	assert.ElementsMatch(t, expect, dump)
}

func TestInternal_GatewayServiceDump_Ingress_ACL(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	dir1, s1 := testServerWithConfig(t, func(c *Config) {
		c.PrimaryDatacenter = "dc1"
		c.ACLsEnabled = true
		c.ACLInitialManagementToken = "root"
		c.ACLResolverSettings.ACLDefaultPolicy = "deny"
	})
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()
	codec := rpcClient(t, s1)
	defer codec.Close()

	testrpc.WaitForTestAgent(t, s1.RPC, "dc1", testrpc.WithToken("root"))

	// Create the ACL.
	token, err := upsertTestTokenWithPolicyRules(codec, "root", "dc1", `
  	service "db" { policy = "read" }
	service "ingress-gateway" { policy = "read" }
	node_prefix "" { policy = "read" }`)
	require.NoError(t, err)

	// Register gateway and two service instances that will be associated with it
	{
		arg := structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "foo",
			Address:    "127.0.0.1",
			Service: &structs.NodeService{
				ID:      "ingress-gateway",
				Service: "ingress-gateway",
				Kind:    structs.ServiceKindIngressGateway,
			},
			Check: &structs.HealthCheck{
				Name:      "ingress connect",
				Status:    api.HealthPassing,
				ServiceID: "ingress-gateway",
			},
			WriteRequest: structs.WriteRequest{Token: "root"},
		}
		var out struct{}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out))

		arg = structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "bar",
			Address:    "127.0.0.2",
			Service: &structs.NodeService{
				ID:      "db",
				Service: "db",
			},
			Check: &structs.HealthCheck{
				Name:      "db-warning",
				Status:    api.HealthWarning,
				ServiceID: "db",
			},
			WriteRequest: structs.WriteRequest{Token: "root"},
		}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out))

		arg = structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "baz",
			Address:    "127.0.0.3",
			Service: &structs.NodeService{
				ID:      "api",
				Service: "api",
			},
			Check: &structs.HealthCheck{
				Name:      "api-passing",
				Status:    api.HealthPassing,
				ServiceID: "api",
			},
			WriteRequest: structs.WriteRequest{Token: "root"},
		}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out))
	}

	// Register ingress-gateway config entry, linking it to db and api
	{
		args := &structs.IngressGatewayConfigEntry{
			Name: "ingress-gateway",
			Kind: structs.IngressGateway,
			Listeners: []structs.IngressListener{
				{
					Port:     8888,
					Protocol: "tcp",
					Services: []structs.IngressService{
						{
							Name: "db",
						},
					},
				},
				{
					Port:     8080,
					Protocol: "tcp",
					Services: []structs.IngressService{
						{
							Name: "web",
						},
					},
				},
			},
		}

		req := structs.ConfigEntryRequest{
			Op:           structs.ConfigEntryUpsert,
			Datacenter:   "dc1",
			Entry:        args,
			WriteRequest: structs.WriteRequest{Token: "root"},
		}
		var out bool
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "ConfigEntry.Apply", &req, &out))
		require.True(t, out)
	}

	var out structs.IndexedServiceDump

	// Not passing a token with service:read on Gateway leads to PermissionDenied
	req := structs.ServiceSpecificRequest{
		Datacenter:  "dc1",
		ServiceName: "ingress-gateway",
	}
	err = msgpackrpc.CallWithCodec(codec, "Internal.GatewayServiceDump", &req, &out)
	require.Error(t, err, acl.ErrPermissionDenied)

	// Passing a token without service:read on api leads to it getting filtered out
	req = structs.ServiceSpecificRequest{
		Datacenter:   "dc1",
		ServiceName:  "ingress-gateway",
		QueryOptions: structs.QueryOptions{Token: token.SecretID},
	}
	require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.GatewayServiceDump", &req, &out))

	nodes := out.Dump
	require.Len(t, nodes, 1)
	require.Equal(t, nodes[0].Node.Node, "bar")
	require.Equal(t, nodes[0].Service.Service, "db")
	require.Equal(t, nodes[0].Checks[0].Status, api.HealthWarning)
}

func TestInternal_ServiceDump_Peering(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	_, s1 := testServerWithConfig(t, func(config *Config) {
		config.PeeringTestAllowPeerRegistrations = true
	})
	codec := rpcClient(t, s1)

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	// prep the cluster with some data we can use in our filters
	registerTestCatalogEntries(t, codec)

	doRequest := func(t *testing.T, filter string, onlyNodes bool) structs.IndexedNodesWithGateways {
		t.Helper()
		args := structs.ServiceDumpRequest{
			QueryOptions: structs.QueryOptions{Filter: filter},
			NodesOnly:    onlyNodes,
		}

		var out structs.IndexedNodesWithGateways
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &args, &out))

		return out
	}

	t.Run("No peerings", func(t *testing.T) {
		nodes := doRequest(t, "", false)
		// redis (3), web (3), critical (1), warning (1) and consul (1)
		require.Len(t, nodes.Nodes, 9)
		require.Len(t, nodes.ImportedNodes, 0)
	})

	addPeerService(t, codec)

	err := s1.fsm.State().PeeringWrite(1, &pbpeering.PeeringWriteRequest{
		Peering: &pbpeering.Peering{
			ID:   "9e650110-ac74-4c5a-a6a8-9348b2bed4e9",
			Name: "peer1",
		},
	})
	require.NoError(t, err)

	t.Run("peerings", func(t *testing.T) {
		nodes := doRequest(t, "", false)
		// redis (3), web (3), critical (1), warning (1) and consul (1)
		require.Len(t, nodes.Nodes, 9)
		// service (1)
		require.Len(t, nodes.ImportedNodes, 1)
	})

	t.Run("peerings onlynodes", func(t *testing.T) {
		nodes := doRequest(t, "", true)
		// redis (3), web (3), critical (1), warning (1) and consul (1)
		require.Len(t, nodes.Nodes, 9)
		// service (1)
		require.Len(t, nodes.ImportedNodes, 0)
	})

	t.Run("peerings w filter", func(t *testing.T) {
		nodes := doRequest(t, "Node.PeerName == foo", false)
		require.Len(t, nodes.Nodes, 0)
		require.Len(t, nodes.ImportedNodes, 0)

		nodes2 := doRequest(t, "Node.PeerName == peer1", false)
		require.Len(t, nodes2.Nodes, 0)
		require.Len(t, nodes2.ImportedNodes, 1)
	})
}

func addPeerService(t *testing.T, codec rpc.ClientCodec) {
	// prep the cluster with some data we can use in our filters
	registrations := map[string]*structs.RegisterRequest{
		"Peer node foo with peer service": {
			Datacenter: "dc1",
			Node:       "foo",
			ID:         types.NodeID("e0155642-135d-4739-9853-a1ee6c9f945b"),
			Address:    "127.0.0.2",
			PeerName:   "peer1",
			Service: &structs.NodeService{
				Kind:     structs.ServiceKindTypical,
				ID:       "serviceID",
				Service:  "service",
				Port:     1235,
				Address:  "198.18.1.2",
				PeerName: "peer1",
			},
		},
	}

	registerTestCatalogEntriesMap(t, codec, registrations)
}

func TestInternal_GatewayIntentions(t *testing.T) {
	netutil.GetAgentBindAddrFunc = netutil.GetMockGetAgentBindAddrFunc("0.0.0.0")
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()

	codec := rpcClient(t, s1)
	defer codec.Close()

	testrpc.WaitForTestAgent(t, s1.RPC, "dc1")

	// Register terminating gateway and config entry linking it to postgres + redis
	{
		arg := structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "foo",
			Address:    "127.0.0.1",
			Service: &structs.NodeService{
				ID:      "terminating-gateway",
				Service: "terminating-gateway",
				Kind:    structs.ServiceKindTerminatingGateway,
				Port:    443,
			},
			Check: &structs.HealthCheck{
				Name:      "terminating connect",
				Status:    api.HealthPassing,
				ServiceID: "terminating-gateway",
			},
		}
		var regOutput struct{}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &regOutput))

		args := &structs.TerminatingGatewayConfigEntry{
			Name: "terminating-gateway",
			Kind: structs.TerminatingGateway,
			Services: []structs.LinkedService{
				{
					Name: "postgres",
				},
				{
					Name:     "redis",
					CAFile:   "/etc/certs/ca.pem",
					CertFile: "/etc/certs/cert.pem",
					KeyFile:  "/etc/certs/key.pem",
				},
			},
		}

		req := structs.ConfigEntryRequest{
			Op:         structs.ConfigEntryUpsert,
			Datacenter: "dc1",
			Entry:      args,
		}
		var configOutput bool
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "ConfigEntry.Apply", &req, &configOutput))
		require.True(t, configOutput)
	}

	// create some symmetric intentions to ensure we are only matching on destination
	{
		for _, v := range []string{"*", "mysql", "redis", "postgres"} {
			req := structs.IntentionRequest{
				Datacenter: "dc1",
				Op:         structs.IntentionOpCreate,
				Intention:  structs.TestIntention(t),
			}
			req.Intention.SourceName = "api"
			req.Intention.DestinationName = v

			var reply string
			assert.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.Apply", &req, &reply))

			req = structs.IntentionRequest{
				Datacenter: "dc1",
				Op:         structs.IntentionOpCreate,
				Intention:  structs.TestIntention(t),
			}
			req.Intention.SourceName = v
			req.Intention.DestinationName = "api"
			assert.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.Apply", &req, &reply))
		}
	}

	// Request intentions matching the gateway named "terminating-gateway"
	req := structs.IntentionQueryRequest{
		Datacenter: "dc1",
		Match: &structs.IntentionQueryMatch{
			Type: structs.IntentionMatchDestination,
			Entries: []structs.IntentionMatchEntry{
				{
					Namespace: structs.IntentionDefaultNamespace,
					Partition: acl.DefaultPartitionName,
					Name:      "terminating-gateway",
				},
			},
		},
	}
	var reply structs.IndexedIntentions
	assert.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.GatewayIntentions", &req, &reply))
	assert.Len(t, reply.Intentions, 3)

	// Only intentions with linked services as a destination should be returned, and wildcard matches should be deduped
	expected := []string{"postgres", "*", "redis"}
	actual := []string{
		reply.Intentions[0].DestinationName,
		reply.Intentions[1].DestinationName,
		reply.Intentions[2].DestinationName,
	}
	assert.ElementsMatch(t, expected, actual)
}

func TestInternal_GatewayIntentions_aclDeny(t *testing.T) {
	netutil.GetAgentBindAddrFunc = netutil.GetMockGetAgentBindAddrFunc("0.0.0.0")
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	dir1, s1 := testServerWithConfig(t, testServerACLConfig)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()
	codec := rpcClient(t, s1)
	defer codec.Close()

	testrpc.WaitForTestAgent(t, s1.RPC, "dc1", testrpc.WithToken(TestDefaultInitialManagementToken))

	// Register terminating gateway and config entry linking it to postgres + redis
	{
		arg := structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "foo",
			Address:    "127.0.0.1",
			Service: &structs.NodeService{
				ID:      "terminating-gateway",
				Service: "terminating-gateway",
				Kind:    structs.ServiceKindTerminatingGateway,
				Port:    443,
			},
			Check: &structs.HealthCheck{
				Name:      "terminating connect",
				Status:    api.HealthPassing,
				ServiceID: "terminating-gateway",
			},
			WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
		}
		var regOutput struct{}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &regOutput))

		args := &structs.TerminatingGatewayConfigEntry{
			Name: "terminating-gateway",
			Kind: structs.TerminatingGateway,
			Services: []structs.LinkedService{
				{
					Name: "postgres",
				},
				{
					Name:     "redis",
					CAFile:   "/etc/certs/ca.pem",
					CertFile: "/etc/certs/cert.pem",
					KeyFile:  "/etc/certs/key.pem",
				},
			},
		}

		req := structs.ConfigEntryRequest{
			Op:           structs.ConfigEntryUpsert,
			Datacenter:   "dc1",
			Entry:        args,
			WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
		}
		var configOutput bool
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "ConfigEntry.Apply", &req, &configOutput))
		require.True(t, configOutput)
	}

	// create some symmetric intentions to ensure we are only matching on destination
	{
		for _, v := range []string{"*", "mysql", "redis", "postgres"} {
			req := structs.IntentionRequest{
				Datacenter:   "dc1",
				Op:           structs.IntentionOpCreate,
				Intention:    structs.TestIntention(t),
				WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
			}
			req.Intention.SourceName = "api"
			req.Intention.DestinationName = v

			var reply string
			assert.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.Apply", &req, &reply))

			req = structs.IntentionRequest{
				Datacenter:   "dc1",
				Op:           structs.IntentionOpCreate,
				Intention:    structs.TestIntention(t),
				WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
			}
			req.Intention.SourceName = v
			req.Intention.DestinationName = "api"
			assert.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.Apply", &req, &reply))
		}
	}

	userToken, err := upsertTestTokenWithPolicyRules(codec, TestDefaultInitialManagementToken, "dc1", `
service_prefix "redis" { policy = "read" }
service_prefix "terminating-gateway" { policy = "read" }
`)
	require.NoError(t, err)

	// Request intentions matching the gateway named "terminating-gateway"
	req := structs.IntentionQueryRequest{
		Datacenter: "dc1",
		Match: &structs.IntentionQueryMatch{
			Type: structs.IntentionMatchDestination,
			Entries: []structs.IntentionMatchEntry{
				{
					Namespace: structs.IntentionDefaultNamespace,
					Partition: acl.DefaultPartitionName,
					Name:      "terminating-gateway",
				},
			},
		},
		QueryOptions: structs.QueryOptions{Token: userToken.SecretID},
	}
	var reply structs.IndexedIntentions
	assert.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.GatewayIntentions", &req, &reply))
	assert.Len(t, reply.Intentions, 2)

	// Only intentions for redis should be returned, due to ACLs
	expected := []string{"*", "redis"}
	actual := []string{
		reply.Intentions[0].DestinationName,
		reply.Intentions[1].DestinationName,
	}
	assert.ElementsMatch(t, expected, actual)
}

func TestInternal_ServiceTopology(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	codec := rpcClient(t, s1)
	defer codec.Close()

	// wildcard deny intention
	// ingress-gateway on node edge - upstream: api
	// ingress -> api gateway config entry (but no intention)

	// api and api-proxy on node foo - transparent proxy
	// api -> web exact intention

	// web and web-proxy on node bar - upstream: redis
	// web and web-proxy on node baz - transparent proxy
	// web -> redis exact intention

	// redis and redis-proxy on node zip

	registerTestTopologyEntries(t, codec, "")

	var (
		ingress = structs.NewServiceName("ingress", structs.DefaultEnterpriseMetaInDefaultPartition())
		api     = structs.NewServiceName("api", structs.DefaultEnterpriseMetaInDefaultPartition())
		web     = structs.NewServiceName("web", structs.DefaultEnterpriseMetaInDefaultPartition())
		redis   = structs.NewServiceName("redis", structs.DefaultEnterpriseMetaInDefaultPartition())
	)

	t.Run("ingress", func(t *testing.T) {
		retry.Run(t, func(r *retry.R) {
			args := structs.ServiceSpecificRequest{
				Datacenter:  "dc1",
				ServiceName: "ingress",
			}
			var out structs.IndexedServiceTopology
			require.NoError(r, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out))
			require.False(r, out.FilteredByACLs)
			require.False(r, out.ResultsFilteredByACLs)
			require.Equal(r, "http", out.ServiceTopology.MetricsProtocol)

			// foo/api, foo/api-proxy
			require.Len(r, out.ServiceTopology.Upstreams, 2)
			require.Len(r, out.ServiceTopology.Downstreams, 0)

			expectUp := map[string]structs.IntentionDecisionSummary{
				api.String(): {
					DefaultAllow:   true,
					Allowed:        false,
					HasPermissions: false,
					ExternalSource: "nomad",

					// From wildcard deny
					HasExact: false,
				},
			}
			require.Equal(r, expectUp, out.ServiceTopology.UpstreamDecisions)

			expectUpstreamSources := map[string]string{
				api.String(): structs.TopologySourceRegistration,
			}
			require.Equal(r, expectUpstreamSources, out.ServiceTopology.UpstreamSources)
			require.Empty(r, out.ServiceTopology.DownstreamSources)

			// The ingress gateway has an explicit upstream
			require.False(r, out.ServiceTopology.TransparentProxy)
		})
	})

	t.Run("api", func(t *testing.T) {
		retry.Run(t, func(r *retry.R) {
			args := structs.ServiceSpecificRequest{
				Datacenter:  "dc1",
				ServiceName: "api",
			}
			var out structs.IndexedServiceTopology
			require.NoError(r, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out))
			require.False(r, out.FilteredByACLs)
			require.False(r, out.ResultsFilteredByACLs)
			require.Equal(r, "http", out.ServiceTopology.MetricsProtocol)

			// edge/ingress
			require.Len(r, out.ServiceTopology.Downstreams, 1)

			expectDown := map[string]structs.IntentionDecisionSummary{
				ingress.String(): {
					DefaultAllow:   true,
					Allowed:        false,
					HasPermissions: false,
					ExternalSource: "nomad",

					// From wildcard deny
					HasExact: false,
				},
			}
			require.Equal(r, expectDown, out.ServiceTopology.DownstreamDecisions)

			expectDownstreamSources := map[string]string{
				ingress.String(): structs.TopologySourceRegistration,
			}
			require.Equal(r, expectDownstreamSources, out.ServiceTopology.DownstreamSources)

			// bar/web, bar/web-proxy, baz/web, baz/web-proxy
			require.Len(r, out.ServiceTopology.Upstreams, 4)

			expectUp := map[string]structs.IntentionDecisionSummary{
				web.String(): {
					DefaultAllow:   true,
					Allowed:        true,
					HasPermissions: false,
					HasExact:       true,
				},
			}
			require.Equal(r, expectUp, out.ServiceTopology.UpstreamDecisions)

			expectUpstreamSources := map[string]string{
				web.String(): structs.TopologySourceSpecificIntention,
			}
			require.Equal(r, expectUpstreamSources, out.ServiceTopology.UpstreamSources)

			// The only instance of api's proxy is in transparent mode
			require.True(r, out.ServiceTopology.TransparentProxy)
		})
	})

	t.Run("web", func(t *testing.T) {
		retry.Run(t, func(r *retry.R) {
			args := structs.ServiceSpecificRequest{
				Datacenter:  "dc1",
				ServiceName: "web",
			}
			var out structs.IndexedServiceTopology
			require.NoError(r, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out))
			require.False(r, out.FilteredByACLs)
			require.False(r, out.ResultsFilteredByACLs)
			require.Equal(r, "http", out.ServiceTopology.MetricsProtocol)

			// foo/api, foo/api-proxy
			require.Len(r, out.ServiceTopology.Downstreams, 2)

			expectDown := map[string]structs.IntentionDecisionSummary{
				api.String(): {
					DefaultAllow:   true,
					Allowed:        true,
					HasPermissions: false,
					HasExact:       true,
				},
			}
			require.Equal(r, expectDown, out.ServiceTopology.DownstreamDecisions)

			expectDownstreamSources := map[string]string{
				api.String(): structs.TopologySourceSpecificIntention,
			}
			require.Equal(r, expectDownstreamSources, out.ServiceTopology.DownstreamSources)

			// zip/redis, zip/redis-proxy
			require.Len(r, out.ServiceTopology.Upstreams, 2)

			expectUp := map[string]structs.IntentionDecisionSummary{
				redis.String(): {
					DefaultAllow:   true,
					Allowed:        false,
					HasPermissions: true,
					HasExact:       true,
				},
			}
			require.Equal(r, expectUp, out.ServiceTopology.UpstreamDecisions)

			expectUpstreamSources := map[string]string{
				// We prefer from-registration over intention source when there is a mix
				redis.String(): structs.TopologySourceRegistration,
			}
			require.Equal(r, expectUpstreamSources, out.ServiceTopology.UpstreamSources)

			// Not all instances of web are in transparent mode
			require.False(r, out.ServiceTopology.TransparentProxy)
		})
	})

	t.Run("redis", func(t *testing.T) {
		retry.Run(t, func(r *retry.R) {
			args := structs.ServiceSpecificRequest{
				Datacenter:  "dc1",
				ServiceName: "redis",
			}
			var out structs.IndexedServiceTopology
			require.NoError(r, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out))
			require.False(r, out.FilteredByACLs)
			require.False(r, out.ResultsFilteredByACLs)
			require.Equal(r, "http", out.ServiceTopology.MetricsProtocol)

			require.Len(r, out.ServiceTopology.Upstreams, 0)

			// bar/web, bar/web-proxy, baz/web, baz/web-proxy
			require.Len(r, out.ServiceTopology.Downstreams, 4)

			expectDown := map[string]structs.IntentionDecisionSummary{
				web.String(): {
					DefaultAllow:   true,
					Allowed:        false,
					HasPermissions: true,
					HasExact:       true,
				},
			}
			require.Equal(r, expectDown, out.ServiceTopology.DownstreamDecisions)

			expectDownstreamSources := map[string]string{
				web.String(): structs.TopologySourceRegistration,
			}
			require.Equal(r, expectDownstreamSources, out.ServiceTopology.DownstreamSources)
			require.Empty(r, out.ServiceTopology.UpstreamSources)

			// No proxies are in transparent mode
			require.False(r, out.ServiceTopology.TransparentProxy)
		})
	})
}

func TestInternal_ServiceTopology_RoutingConfig(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	codec := rpcClient(t, s1)
	defer codec.Close()

	// dashboard -> routing-config -> { counting, counting-v2 }
	registerTestRoutingConfigTopologyEntries(t, codec)

	t.Run("dashboard", func(t *testing.T) {
		retry.Run(t, func(r *retry.R) {
			args := structs.ServiceSpecificRequest{
				Datacenter:  "dc1",
				ServiceName: "dashboard",
			}
			var out structs.IndexedServiceTopology
			require.NoError(r, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out))
			require.False(r, out.FilteredByACLs)
			require.False(r, out.ResultsFilteredByACLs)
			require.Equal(r, "http", out.ServiceTopology.MetricsProtocol)

			require.Empty(r, out.ServiceTopology.Downstreams)
			require.Empty(r, out.ServiceTopology.DownstreamDecisions)
			require.Empty(r, out.ServiceTopology.DownstreamSources)

			// routing-config will not appear as an Upstream service
			// but will be present in UpstreamSources as a k-v pair.
			require.Empty(r, out.ServiceTopology.Upstreams)

			sn := structs.NewServiceName("routing-config", structs.DefaultEnterpriseMetaInDefaultPartition()).String()

			expectUp := map[string]structs.IntentionDecisionSummary{
				sn: {DefaultAllow: true, Allowed: true},
			}
			require.Equal(r, expectUp, out.ServiceTopology.UpstreamDecisions)

			expectUpstreamSources := map[string]string{
				sn: structs.TopologySourceRoutingConfig,
			}
			require.Equal(r, expectUpstreamSources, out.ServiceTopology.UpstreamSources)

			require.False(r, out.ServiceTopology.TransparentProxy)
		})
	})
}

func TestInternal_ServiceTopology_ACL(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	_, s1 := testServerWithConfig(t, func(c *Config) {
		c.PrimaryDatacenter = "dc1"
		c.ACLsEnabled = true
		c.ACLInitialManagementToken = TestDefaultInitialManagementToken
		c.ACLResolverSettings.ACLDefaultPolicy = "deny"
	})

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	codec := rpcClient(t, s1)
	defer codec.Close()

	// wildcard deny intention
	// ingress-gateway on node edge - upstream: api
	// ingress -> api gateway config entry (but no intention)

	// api and api-proxy on node foo - transparent proxy
	// api -> web exact intention

	// web and web-proxy on node bar - upstream: redis
	// web and web-proxy on node baz - transparent proxy
	// web -> redis exact intention

	// redis and redis-proxy on node zip
	registerTestTopologyEntries(t, codec, TestDefaultInitialManagementToken)

	// Token grants read to: foo/api, foo/api-proxy, bar/web, baz/web
	userToken, err := upsertTestTokenWithPolicyRules(codec, TestDefaultInitialManagementToken, "dc1", `
node_prefix "" { policy = "read" }
service_prefix "api" { policy = "read" }
service "web" { policy = "read" }
`)
	require.NoError(t, err)

	t.Run("api can't read web", func(t *testing.T) {
		args := structs.ServiceSpecificRequest{
			Datacenter:   "dc1",
			ServiceName:  "api",
			QueryOptions: structs.QueryOptions{Token: userToken.SecretID},
		}
		var out structs.IndexedServiceTopology
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out))

		require.True(t, out.FilteredByACLs)
		require.True(t, out.ResultsFilteredByACLs)
		require.Equal(t, "http", out.ServiceTopology.MetricsProtocol)

		// The web-proxy upstream gets filtered out from both bar and baz
		require.Len(t, out.ServiceTopology.Upstreams, 2)
		require.Equal(t, "web", out.ServiceTopology.Upstreams[0].Service.Service)
		require.Equal(t, "web", out.ServiceTopology.Upstreams[1].Service.Service)

		require.Len(t, out.ServiceTopology.Downstreams, 0)
	})

	t.Run("web can't read redis", func(t *testing.T) {
		args := structs.ServiceSpecificRequest{
			Datacenter:   "dc1",
			ServiceName:  "web",
			QueryOptions: structs.QueryOptions{Token: userToken.SecretID},
		}
		var out structs.IndexedServiceTopology
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out))

		require.True(t, out.FilteredByACLs)
		require.True(t, out.ResultsFilteredByACLs)
		require.Equal(t, "http", out.ServiceTopology.MetricsProtocol)

		// The redis upstream gets filtered out but the api and proxy downstream are returned
		require.Len(t, out.ServiceTopology.Upstreams, 0)
		require.Len(t, out.ServiceTopology.Downstreams, 2)
	})

	t.Run("redis can't read self", func(t *testing.T) {
		args := structs.ServiceSpecificRequest{
			Datacenter:   "dc1",
			ServiceName:  "redis",
			QueryOptions: structs.QueryOptions{Token: userToken.SecretID},
		}
		var out structs.IndexedServiceTopology
		err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out)

		// Can't read self, fails fast
		require.True(t, acl.IsErrPermissionDenied(err))
	})
}

// Tests that default intention deny policy overrides the ACL allow policy.
// More comprehensive tests are done at the state store so this is minimal
// coverage to be confident that the override happens.
func TestInternal_ServiceTopology_DefaultIntentionPolicy(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	_, s1 := testServerWithConfig(t, func(c *Config) {
		c.PrimaryDatacenter = "dc1"
		c.ACLsEnabled = true
		c.ACLInitialManagementToken = TestDefaultInitialManagementToken
		c.ACLResolverSettings.ACLDefaultPolicy = "allow"
		c.DefaultIntentionPolicy = "deny"
	})

	testrpc.WaitForLeader(t, s1.RPC, "dc1")
	codec := rpcClient(t, s1)

	registerTestTopologyEntries(t, codec, TestDefaultInitialManagementToken)

	args := structs.ServiceSpecificRequest{
		Datacenter:   "dc1",
		ServiceName:  "redis",
		QueryOptions: structs.QueryOptions{Token: TestDefaultInitialManagementToken},
	}
	var out structs.IndexedServiceTopology
	require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out))

	webSN := structs.NewServiceName("web", acl.DefaultEnterpriseMeta())
	require.False(t, out.ServiceTopology.DownstreamDecisions[webSN.String()].DefaultAllow)
}

func TestInternal_IntentionUpstreams(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	codec := rpcClient(t, s1)
	defer codec.Close()

	// Services:
	// api and api-proxy on node foo
	// web and web-proxy on node foo
	//
	// Intentions
	// * -> * (deny) intention
	// web -> api (allow)
	registerIntentionUpstreamEntries(t, codec, "")

	t.Run("web", func(t *testing.T) {
		retry.Run(t, func(r *retry.R) {
			args := structs.ServiceSpecificRequest{
				Datacenter:  "dc1",
				ServiceName: "web",
			}
			var out structs.IndexedServiceList
			require.NoError(r, msgpackrpc.CallWithCodec(codec, "Internal.IntentionUpstreams", &args, &out))

			// foo/api
			require.Len(r, out.Services, 1)

			expectUp := structs.ServiceList{
				structs.NewServiceName("api", structs.DefaultEnterpriseMetaInDefaultPartition()),
			}
			require.Equal(r, expectUp, out.Services)
		})
	})
}

func TestInternal_IntentionUpstreamsDestination(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	codec := rpcClient(t, s1)
	defer codec.Close()

	// Services:
	// api and api-proxy on node foo
	// web and web-proxy on node foo
	//
	// Intentions
	// * -> * (deny) intention
	// web -> api (allow)
	registerIntentionUpstreamEntries(t, codec, "")

	t.Run("api.example.com", func(t *testing.T) {
		retry.Run(t, func(r *retry.R) {
			args := structs.ServiceSpecificRequest{
				Datacenter:  "dc1",
				ServiceName: "web",
			}
			var out structs.IndexedServiceList
			require.NoError(r, msgpackrpc.CallWithCodec(codec, "Internal.IntentionUpstreamsDestination", &args, &out))

			// foo/api
			require.Len(r, out.Services, 1)

			expectUp := structs.ServiceList{
				structs.NewServiceName("api.example.com", structs.DefaultEnterpriseMetaInDefaultPartition()),
			}
			require.Equal(r, expectUp, out.Services)
		})
	})
}

func TestInternal_IntentionUpstreams_BlockOnNoChange(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()

	_, s1 := testServerWithConfig(t, func(c *Config) {
		c.DevMode = true // keep it in ram to make it 10x faster on macos
	})

	codec := rpcClient(t, s1)

	waitForLeaderEstablishment(t, s1)

	{ // ensure it's default deny to start
		var out bool
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "ConfigEntry.Apply", &structs.ConfigEntryRequest{
			Entry: &structs.ServiceIntentionsConfigEntry{
				Kind: structs.ServiceIntentions,
				Name: "*",
				Sources: []*structs.SourceIntention{
					{
						Name:   "*",
						Action: structs.IntentionActionDeny,
					},
				},
			},
		}, &out))
		require.True(t, out)
	}

	run := func(t *testing.T, dataPrefix string, expectServices int) {
		rpcBlockingQueryTestHarness(t,
			func(minQueryIndex uint64) (*structs.QueryMeta, <-chan error) {
				args := &structs.ServiceSpecificRequest{
					Datacenter:  "dc1",
					ServiceName: "web",
				}
				args.MinQueryIndex = minQueryIndex

				var out structs.IndexedServiceList
				errCh := channelCallRPC(s1, "Internal.IntentionUpstreams", args, &out, func() error {
					if len(out.Services) != expectServices {
						return fmt.Errorf("expected %d services got %d", expectServices, len(out.Services))
					}
					return nil
				})
				return &out.QueryMeta, errCh
			},
			func(i int) <-chan error {
				var out string
				return channelCallRPC(s1, "Intention.Apply", &structs.IntentionRequest{
					Datacenter: "dc1",
					Op:         structs.IntentionOpCreate,
					Intention: &structs.Intention{
						SourceName:      fmt.Sprintf(dataPrefix+"-src-%d", i),
						DestinationName: fmt.Sprintf(dataPrefix+"-dst-%d", i),
						Action:          structs.IntentionActionAllow,
					},
				}, &out, nil)
			},
		)
	}

	testutil.RunStep(t, "test the errNotFound path", func(t *testing.T) {
		run(t, "other", 0)
	})

	// Services:
	// api and api-proxy on node foo
	// web and web-proxy on node foo
	//
	// Intentions
	// * -> * (deny) intention
	// web -> api (allow)
	registerIntentionUpstreamEntries(t, codec, "")

	testutil.RunStep(t, "test the errNotChanged path", func(t *testing.T) {
		run(t, "completely-different-other", 1)
	})
}

func TestInternal_IntentionUpstreams_ACL(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	dir1, s1 := testServerWithConfig(t, func(c *Config) {
		c.PrimaryDatacenter = "dc1"
		c.ACLsEnabled = true
		c.ACLInitialManagementToken = TestDefaultInitialManagementToken
		c.ACLResolverSettings.ACLDefaultPolicy = "deny"
	})
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	codec := rpcClient(t, s1)
	defer codec.Close()

	// Services:
	// api and api-proxy on node foo
	// web and web-proxy on node foo
	//
	// Intentions
	// * -> * (deny) intention
	// web -> api (allow)
	registerIntentionUpstreamEntries(t, codec, TestDefaultInitialManagementToken)

	t.Run("valid token", func(t *testing.T) {
		// Token grants read to read api service
		userToken, err := upsertTestTokenWithPolicyRules(codec, TestDefaultInitialManagementToken, "dc1", `
service_prefix "api" { policy = "read" }
`)
		require.NoError(t, err)

		retry.Run(t, func(r *retry.R) {
			args := structs.ServiceSpecificRequest{
				Datacenter:   "dc1",
				ServiceName:  "web",
				QueryOptions: structs.QueryOptions{Token: userToken.SecretID},
			}
			var out structs.IndexedServiceList
			require.NoError(r, msgpackrpc.CallWithCodec(codec, "Internal.IntentionUpstreams", &args, &out))

			// foo/api
			require.Len(r, out.Services, 1)

			expectUp := structs.ServiceList{
				structs.NewServiceName("api", structs.DefaultEnterpriseMetaInDefaultPartition()),
			}
			require.Equal(r, expectUp, out.Services)
		})
	})

	t.Run("invalid token filters results", func(t *testing.T) {
		// Token grants read to read an unrelated service, mongo
		userToken, err := upsertTestTokenWithPolicyRules(codec, TestDefaultInitialManagementToken, "dc1", `
service_prefix "mongo" { policy = "read" }
`)
		require.NoError(t, err)

		retry.Run(t, func(r *retry.R) {
			args := structs.ServiceSpecificRequest{
				Datacenter:   "dc1",
				ServiceName:  "web",
				QueryOptions: structs.QueryOptions{Token: userToken.SecretID},
			}
			var out structs.IndexedServiceList
			require.NoError(r, msgpackrpc.CallWithCodec(codec, "Internal.IntentionUpstreams", &args, &out))

			// Token can't read api service
			require.Empty(r, out.Services)
		})
	})
}

func TestInternal_CatalogOverview(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	_, s1 := testServerWithConfig(t, func(c *Config) {
		c.MetricsReportingInterval = 100 * time.Millisecond
	})
	codec := rpcClient(t, s1)

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	arg := structs.DCSpecificRequest{
		Datacenter: "dc1",
	}
	retry.Run(t, func(r *retry.R) {
		var out structs.CatalogSummary
		if err := msgpackrpc.CallWithCodec(codec, "Internal.CatalogOverview", &arg, &out); err != nil {
			r.Fatalf("err: %v", err)
		}

		expected := structs.CatalogSummary{
			Nodes: []structs.HealthSummary{
				{
					Total:          1,
					Passing:        1,
					EnterpriseMeta: *structs.NodeEnterpriseMetaInDefaultPartition(),
				},
			},
			Services: []structs.HealthSummary{
				{
					Name:           "consul",
					Total:          1,
					Passing:        1,
					EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
				},
			},
			Checks: []structs.HealthSummary{
				{
					Name:           "Serf Health Status",
					Total:          1,
					Passing:        1,
					EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
				},
			},
		}
		require.Equal(r, expected, out)
	})
}

func TestInternal_CatalogOverview_ACLDeny(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()

	_, s1 := testServerWithConfig(t, func(c *Config) {
		c.PrimaryDatacenter = "dc1"
		c.ACLsEnabled = true
		c.ACLInitialManagementToken = TestDefaultInitialManagementToken
		c.ACLResolverSettings.ACLDefaultPolicy = "deny"
	})
	codec := rpcClient(t, s1)

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	arg := structs.DCSpecificRequest{
		Datacenter: "dc1",
	}
	var out structs.CatalogSummary
	err := msgpackrpc.CallWithCodec(codec, "Internal.CatalogOverview", &arg, &out)
	require.True(t, acl.IsErrPermissionDenied(err))

	opReadToken, err := upsertTestTokenWithPolicyRules(
		codec, TestDefaultInitialManagementToken, "dc1", `operator = "read"`)
	require.NoError(t, err)

	arg.Token = opReadToken.SecretID
	require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.CatalogOverview", &arg, &out))
}

func TestInternal_PeeredUpstreams(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	orig := virtualIPVersionCheckInterval
	virtualIPVersionCheckInterval = 50 * time.Millisecond
	t.Cleanup(func() { virtualIPVersionCheckInterval = orig })

	t.Parallel()
	_, s1 := testServerWithConfig(t)

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	// Services
	//   api        local
	//   web        peer: peer-a
	//   web-proxy  peer: peer-a
	//   web        peer: peer-b
	//   web-proxy  peer: peer-b
	registerLocalAndRemoteServicesVIPEnabled(t, s1.fsm.State())

	codec := rpcClient(t, s1)

	args := structs.PartitionSpecificRequest{
		Datacenter:     "dc1",
		EnterpriseMeta: *acl.DefaultEnterpriseMeta(),
	}
	var out structs.IndexedPeeredServiceList
	require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.PeeredUpstreams", &args, &out))

	require.Len(t, out.Services, 2)
	expect := []structs.PeeredServiceName{
		{Peer: "peer-a", ServiceName: structs.NewServiceName("web", structs.DefaultEnterpriseMetaInDefaultPartition())},
		{Peer: "peer-b", ServiceName: structs.NewServiceName("web", structs.DefaultEnterpriseMetaInDefaultPartition())},
	}
	require.Equal(t, expect, out.Services)
}

func TestInternal_ServiceGatewayService_Terminating(t *testing.T) {
	netutil.GetAgentBindAddrFunc = netutil.GetMockGetAgentBindAddrFunc("0.0.0.0")
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()
	codec := rpcClient(t, s1)
	defer codec.Close()

	testrpc.WaitForTestAgent(t, s1.RPC, "dc1")

	db := structs.NodeService{
		ID:      "db2",
		Service: "db",
	}

	redis := structs.NodeService{
		ID:      "redis",
		Service: "redis",
	}

	// Register gateway and two service instances that will be associated with it
	{
		arg := structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "foo",
			Address:    "10.1.2.2",
			Service: &structs.NodeService{
				ID:      "terminating-gateway-01",
				Service: "terminating-gateway",
				Kind:    structs.ServiceKindTerminatingGateway,
				Port:    443,
				Address: "198.18.1.3",
			},
			Check: &structs.HealthCheck{
				Name:      "terminating connect",
				Status:    api.HealthPassing,
				ServiceID: "terminating-gateway-01",
			},
		}
		var out struct{}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out))

		arg = structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "bar",
			Address:    "127.0.0.2",
			Service: &structs.NodeService{
				ID:      "db",
				Service: "db",
			},
			Check: &structs.HealthCheck{
				Name:      "db-warning",
				Status:    api.HealthWarning,
				ServiceID: "db",
			},
		}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out))

		arg = structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "baz",
			Address:    "127.0.0.3",
			Service:    &db,
			Check: &structs.HealthCheck{
				Name:      "db2-passing",
				Status:    api.HealthPassing,
				ServiceID: "db2",
			},
		}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out))
	}

	// Register terminating-gateway config entry, linking it to db and redis (dne)
	{
		args := &structs.TerminatingGatewayConfigEntry{
			Name: "terminating-gateway",
			Kind: structs.TerminatingGateway,
			Services: []structs.LinkedService{
				{
					Name: "db",
				},
				{
					Name:     "redis",
					CAFile:   "/etc/certs/ca.pem",
					CertFile: "/etc/certs/cert.pem",
					KeyFile:  "/etc/certs/key.pem",
				},
			},
		}

		req := structs.ConfigEntryRequest{
			Op:         structs.ConfigEntryUpsert,
			Datacenter: "dc1",
			Entry:      args,
		}
		var configOutput bool
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "ConfigEntry.Apply", &req, &configOutput))
		require.True(t, configOutput)
	}

	var out structs.IndexedCheckServiceNodes
	req := structs.ServiceSpecificRequest{
		Datacenter:  "dc1",
		ServiceName: "db",
		ServiceKind: structs.ServiceKindTerminatingGateway,
	}
	require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.ServiceGateways", &req, &out))

	for _, n := range out.Nodes {
		n.Node.RaftIndex = structs.RaftIndex{}
		n.Service.RaftIndex = structs.RaftIndex{}
		for _, m := range n.Checks {
			m.RaftIndex = structs.RaftIndex{}
		}
	}

	expect := structs.CheckServiceNodes{
		structs.CheckServiceNode{
			Node: &structs.Node{
				Node:       "foo",
				RaftIndex:  structs.RaftIndex{},
				Address:    "10.1.2.2",
				Datacenter: "dc1",
				Partition:  acl.DefaultPartitionName,
			},
			Service: &structs.NodeService{
				Kind:    structs.ServiceKindTerminatingGateway,
				ID:      "terminating-gateway-01",
				Service: "terminating-gateway",
				TaggedAddresses: map[string]structs.ServiceAddress{
					"consul-virtual:" + db.CompoundServiceName().String():    {Address: "240.0.0.1"},
					"consul-virtual:" + redis.CompoundServiceName().String(): {Address: "240.0.0.2"},
				},
				Weights:        &structs.Weights{Passing: 1, Warning: 1},
				Port:           443,
				Tags:           []string{},
				Meta:           map[string]string{},
				EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
				RaftIndex:      structs.RaftIndex{},
				Address:        "198.18.1.3",
			},
			Checks: structs.HealthChecks{
				&structs.HealthCheck{
					Name:           "terminating connect",
					Node:           "foo",
					CheckID:        "terminating connect",
					Status:         api.HealthPassing,
					ServiceID:      "terminating-gateway-01",
					ServiceName:    "terminating-gateway",
					EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
				},
			},
		},
	}

	assert.Equal(t, expect, out.Nodes)
}

func TestInternal_ServiceGatewayService_Terminating_ACL(t *testing.T) {
	netutil.GetAgentBindAddrFunc = netutil.GetMockGetAgentBindAddrFunc("0.0.0.0")
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	dir1, s1 := testServerWithConfig(t, func(c *Config) {
		c.PrimaryDatacenter = "dc1"
		c.ACLsEnabled = true
		c.ACLInitialManagementToken = "root"
		c.ACLResolverSettings.ACLDefaultPolicy = "deny"
	})
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()
	codec := rpcClient(t, s1)
	defer codec.Close()

	testrpc.WaitForTestAgent(t, s1.RPC, "dc1", testrpc.WithToken("root"))

	// Create the ACL.
	token, err := upsertTestTokenWithPolicyRules(codec, "root", "dc1", `
  	service "db" { policy = "read" }
	service "terminating-gateway" { policy = "read" }
	node_prefix "" { policy = "read" }`)
	require.NoError(t, err)

	// Register gateway and two service instances that will be associated with it
	{
		arg := structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "foo",
			Address:    "127.0.0.1",
			Service: &structs.NodeService{
				ID:      "terminating-gateway",
				Service: "terminating-gateway",
				Kind:    structs.ServiceKindTerminatingGateway,
				Port:    443,
			},
			Check: &structs.HealthCheck{
				Name:      "terminating connect",
				Status:    api.HealthPassing,
				ServiceID: "terminating-gateway",
			},
			WriteRequest: structs.WriteRequest{Token: "root"},
		}
		var out struct{}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out))
		{
			arg := structs.RegisterRequest{
				Datacenter: "dc1",
				Node:       "foo",
				Address:    "127.0.0.1",
				Service: &structs.NodeService{
					ID:      "terminating-gateway2",
					Service: "terminating-gateway2",
					Kind:    structs.ServiceKindTerminatingGateway,
					Port:    444,
				},
				Check: &structs.HealthCheck{
					Name:      "terminating connect",
					Status:    api.HealthPassing,
					ServiceID: "terminating-gateway2",
				},
				WriteRequest: structs.WriteRequest{Token: "root"},
			}
			var out struct{}
			require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out))
		}

		arg = structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "bar",
			Address:    "127.0.0.2",
			Service: &structs.NodeService{
				ID:      "db",
				Service: "db",
			},
			Check: &structs.HealthCheck{
				Name:      "db-warning",
				Status:    api.HealthWarning,
				ServiceID: "db",
			},
			WriteRequest: structs.WriteRequest{Token: "root"},
		}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out))

		arg = structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "baz",
			Address:    "127.0.0.3",
			Service: &structs.NodeService{
				ID:      "api",
				Service: "api",
			},
			Check: &structs.HealthCheck{
				Name:      "api-passing",
				Status:    api.HealthPassing,
				ServiceID: "api",
			},
			WriteRequest: structs.WriteRequest{Token: "root"},
		}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out))
	}

	// Register terminating-gateway config entry, linking it to db and api
	{
		args := &structs.TerminatingGatewayConfigEntry{
			Name: "terminating-gateway",
			Kind: structs.TerminatingGateway,
			Services: []structs.LinkedService{
				{Name: "db"},
				{Name: "api"},
			},
		}

		req := structs.ConfigEntryRequest{
			Op:           structs.ConfigEntryUpsert,
			Datacenter:   "dc1",
			Entry:        args,
			WriteRequest: structs.WriteRequest{Token: "root"},
		}
		var out bool
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "ConfigEntry.Apply", &req, &out))
		require.True(t, out)
	}

	// Register terminating-gateway config entry, linking it to db and api
	{
		args := &structs.TerminatingGatewayConfigEntry{
			Name: "terminating-gateway2",
			Kind: structs.TerminatingGateway,
			Services: []structs.LinkedService{
				{Name: "db"},
				{Name: "api"},
			},
		}

		req := structs.ConfigEntryRequest{
			Op:           structs.ConfigEntryUpsert,
			Datacenter:   "dc1",
			Entry:        args,
			WriteRequest: structs.WriteRequest{Token: "root"},
		}
		var out bool
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "ConfigEntry.Apply", &req, &out))
		require.True(t, out)
	}

	var out structs.IndexedCheckServiceNodes

	// Not passing a token with service:read on Gateway leads to PermissionDenied
	req := structs.ServiceSpecificRequest{
		Datacenter:  "dc1",
		ServiceName: "db",
		ServiceKind: structs.ServiceKindTerminatingGateway,
	}
	err = msgpackrpc.CallWithCodec(codec, "Internal.ServiceGateways", &req, &out)
	require.Error(t, err, acl.ErrPermissionDenied)

	// Passing a token without service:read on api leads to it getting filtered out
	req = structs.ServiceSpecificRequest{
		Datacenter:   "dc1",
		ServiceName:  "db",
		ServiceKind:  structs.ServiceKindTerminatingGateway,
		QueryOptions: structs.QueryOptions{Token: token.SecretID},
	}
	require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.ServiceGateways", &req, &out))

	nodes := out.Nodes
	require.Len(t, nodes, 1)
	require.Equal(t, "foo", nodes[0].Node.Node)
	require.Equal(t, structs.ServiceKindTerminatingGateway, nodes[0].Service.Kind)
	require.Equal(t, "terminating-gateway", nodes[0].Service.Service)
	require.Equal(t, "terminating-gateway", nodes[0].Service.ID)
	require.True(t, out.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
}

func TestInternal_ServiceGatewayService_Terminating_Destination(t *testing.T) {
	netutil.GetAgentBindAddrFunc = netutil.GetMockGetAgentBindAddrFunc("0.0.0.0")
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}

	t.Parallel()
	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()
	codec := rpcClient(t, s1)
	defer codec.Close()

	testrpc.WaitForTestAgent(t, s1.RPC, "dc1")

	google := structs.NodeService{
		ID:      "google",
		Service: "google",
	}

	// Register service-default with conflicting destination address
	{
		arg := structs.ConfigEntryRequest{
			Op:         structs.ConfigEntryUpsert,
			Datacenter: "dc1",
			Entry: &structs.ServiceConfigEntry{
				Name:           "google",
				Destination:    &structs.DestinationConfig{Addresses: []string{"www.google.com"}, Port: 443},
				EnterpriseMeta: *acl.DefaultEnterpriseMeta(),
			},
		}
		var configOutput bool
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "ConfigEntry.Apply", &arg, &configOutput))
		require.True(t, configOutput)
	}

	// Register terminating-gateway config entry, linking it to google.com
	{
		arg := structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       "foo",
			Address:    "127.0.0.1",
			Service: &structs.NodeService{
				ID:      "terminating-gateway",
				Service: "terminating-gateway",
				Kind:    structs.ServiceKindTerminatingGateway,
				Port:    443,
			},
			Check: &structs.HealthCheck{
				Name:      "terminating connect",
				Status:    api.HealthPassing,
				ServiceID: "terminating-gateway",
			},
		}
		var out struct{}
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out))
	}
	{
		args := &structs.TerminatingGatewayConfigEntry{
			Name: "terminating-gateway",
			Kind: structs.TerminatingGateway,
			Services: []structs.LinkedService{
				{
					Name: "google",
				},
			},
		}

		req := structs.ConfigEntryRequest{
			Op:         structs.ConfigEntryUpsert,
			Datacenter: "dc1",
			Entry:      args,
		}
		var configOutput bool
		require.NoError(t, msgpackrpc.CallWithCodec(codec, "ConfigEntry.Apply", &req, &configOutput))
		require.True(t, configOutput)
	}

	var out structs.IndexedCheckServiceNodes
	req := structs.ServiceSpecificRequest{
		Datacenter:  "dc1",
		ServiceName: "google",
		ServiceKind: structs.ServiceKindTerminatingGateway,
	}
	require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.ServiceGateways", &req, &out))

	nodes := out.Nodes

	for _, n := range nodes {
		n.Node.RaftIndex = structs.RaftIndex{}
		n.Service.RaftIndex = structs.RaftIndex{}
		for _, m := range n.Checks {
			m.RaftIndex = structs.RaftIndex{}
		}
	}

	expect := structs.CheckServiceNodes{
		structs.CheckServiceNode{
			Node: &structs.Node{
				Node:       "foo",
				RaftIndex:  structs.RaftIndex{},
				Address:    "127.0.0.1",
				Datacenter: "dc1",
				Partition:  acl.DefaultPartitionName,
			},
			Service: &structs.NodeService{
				Kind:           structs.ServiceKindTerminatingGateway,
				ID:             "terminating-gateway",
				Service:        "terminating-gateway",
				Weights:        &structs.Weights{Passing: 1, Warning: 1},
				Port:           443,
				Tags:           []string{},
				Meta:           map[string]string{},
				EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
				TaggedAddresses: map[string]structs.ServiceAddress{
					"consul-virtual:" + google.CompoundServiceName().String(): {Address: "240.0.0.1"},
				},
				RaftIndex: structs.RaftIndex{},
				Address:   "",
			},
			Checks: structs.HealthChecks{
				&structs.HealthCheck{
					Name:           "terminating connect",
					Node:           "foo",
					CheckID:        "terminating connect",
					Status:         api.HealthPassing,
					ServiceID:      "terminating-gateway",
					ServiceName:    "terminating-gateway",
					EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
				},
			},
		},
	}

	assert.Len(t, nodes, 1)
	assert.Equal(t, expect, nodes)
}

func TestInternal_ExportedPeeredServices_ACLEnforcement(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}
	t.Parallel()

	_, s := testServerWithConfig(t, testServerACLConfig)
	codec := rpcClient(t, s)

	require.NoError(t, s.fsm.State().PeeringWrite(1, &pbpeering.PeeringWriteRequest{
		Peering: &pbpeering.Peering{
			ID:   testUUID(),
			Name: "peer-1",
		},
	}))
	require.NoError(t, s.fsm.State().PeeringWrite(1, &pbpeering.PeeringWriteRequest{
		Peering: &pbpeering.Peering{
			ID:   testUUID(),
			Name: "peer-2",
		},
	}))
	require.NoError(t, s.fsm.State().EnsureConfigEntry(1, &structs.ExportedServicesConfigEntry{
		Name: "default",
		Services: []structs.ExportedService{
			{
				Name: "web",
				Consumers: []structs.ServiceConsumer{
					{Peer: "peer-1"},
				},
			},
			{
				Name: "db",
				Consumers: []structs.ServiceConsumer{
					{Peer: "peer-2"},
				},
			},
			{
				Name: "api",
				Consumers: []structs.ServiceConsumer{
					{Peer: "peer-1"},
				},
			},
		},
	}))

	type testcase struct {
		name      string
		token     string
		expect    map[string]structs.ServiceList
		expectErr string
	}
	run := func(t *testing.T, tc testcase) {
		var out *structs.IndexedExportedServiceList
		req := structs.DCSpecificRequest{
			Datacenter:   "dc1",
			QueryOptions: structs.QueryOptions{Token: tc.token},
		}
		err := msgpackrpc.CallWithCodec(codec, "Internal.ExportedPeeredServices", &req, &out)

		if tc.expectErr != "" {
			require.Error(t, err)
			require.Contains(t, err.Error(), tc.expectErr)
			require.Nil(t, out)
			return
		}

		require.NoError(t, err)

		require.Len(t, out.Services, len(tc.expect))
		for k, v := range tc.expect {
			require.ElementsMatch(t, v, out.Services[k])
		}
	}
	tcs := []testcase{
		{
			name: "can read all",
			token: tokenWithRules(t, codec, TestDefaultInitialManagementToken,
				`
			service_prefix "" {
				policy = "read"
			}
			`),
			expect: map[string]structs.ServiceList{
				"peer-1": {
					structs.NewServiceName("api", nil),
					structs.NewServiceName("web", nil),
				},
				"peer-2": {
					structs.NewServiceName("db", nil),
				},
			},
		},
		{
			name: "filtered",
			token: tokenWithRules(t, codec, TestDefaultInitialManagementToken,
				`
			service "web" { policy = "read" }
			service "api" { policy = "read" }
			service "db"  { policy = "deny" }
			`),
			expect: map[string]structs.ServiceList{
				"peer-1": {
					structs.NewServiceName("api", nil),
					structs.NewServiceName("web", nil),
				},
			},
		},
	}
	for _, tc := range tcs {
		t.Run(tc.name, func(t *testing.T) {
			run(t, tc)
		})
	}
}

func tokenWithRules(t *testing.T, codec rpc.ClientCodec, mgmtToken, rules string) string {
	t.Helper()

	var tok *structs.ACLToken
	var err error
	retry.Run(t, func(r *retry.R) {
		tok, err = upsertTestTokenWithPolicyRules(codec, mgmtToken, "dc1", rules)
		require.NoError(r, err)
	})
	return tok.SecretID
}

func TestInternal_PeeredUpstreams_ACLEnforcement(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}
	t.Parallel()

	_, s := testServerWithConfig(t, testServerACLConfig)
	codec := rpcClient(t, s)

	type testcase struct {
		name      string
		token     string
		expectErr string
	}
	run := func(t *testing.T, tc testcase) {
		var out *structs.IndexedPeeredServiceList

		req := structs.PartitionSpecificRequest{
			Datacenter:   "dc1",
			QueryOptions: structs.QueryOptions{Token: tc.token},
		}
		err := msgpackrpc.CallWithCodec(codec, "Internal.PeeredUpstreams", &req, &out)

		if tc.expectErr != "" {
			require.Error(t, err)
			require.Contains(t, err.Error(), tc.expectErr)
			require.Nil(t, out)
		} else {
			require.NoError(t, err)
		}
	}
	tcs := []testcase{
		{
			name: "can write all",
			token: tokenWithRules(t, codec, TestDefaultInitialManagementToken, `
			service_prefix "" {
				policy = "write"
			}
			`),
		},
		{
			name:      "can't write",
			token:     tokenWithRules(t, codec, TestDefaultInitialManagementToken, ``),
			expectErr: "lacks permission 'service:write' on \"any service\"",
		},
	}
	for _, tc := range tcs {
		t.Run(tc.name, func(t *testing.T) {
			run(t, tc)
		})
	}
}

func TestInternal_ExportedServicesForPeer_ACLEnforcement(t *testing.T) {
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}
	t.Parallel()

	_, s := testServerWithConfig(t, testServerACLConfig)
	codec := rpcClient(t, s)

	require.NoError(t, s.fsm.State().PeeringWrite(1, &pbpeering.PeeringWriteRequest{
		Peering: &pbpeering.Peering{
			ID:   testUUID(),
			Name: "peer-1",
		},
	}))
	require.NoError(t, s.fsm.State().PeeringWrite(1, &pbpeering.PeeringWriteRequest{
		Peering: &pbpeering.Peering{
			ID:   testUUID(),
			Name: "peer-2",
		},
	}))
	require.NoError(t, s.fsm.State().EnsureConfigEntry(1, &structs.ExportedServicesConfigEntry{
		Name: "default",
		Services: []structs.ExportedService{
			{
				Name: "web",
				Consumers: []structs.ServiceConsumer{
					{Peer: "peer-1"},
				},
			},
			{
				Name: "db",
				Consumers: []structs.ServiceConsumer{
					{Peer: "peer-2"},
				},
			},
			{
				Name: "api",
				Consumers: []structs.ServiceConsumer{
					{Peer: "peer-1"},
				},
			},
		},
	}))

	type testcase struct {
		name      string
		token     string
		expect    structs.ServiceList
		expectErr string
	}
	run := func(t *testing.T, tc testcase) {
		var out *structs.IndexedServiceList
		req := structs.ServiceDumpRequest{
			Datacenter:   "dc1",
			PeerName:     "peer-1",
			QueryOptions: structs.QueryOptions{Token: tc.token},
		}
		err := msgpackrpc.CallWithCodec(codec, "Internal.ExportedServicesForPeer", &req, &out)

		if tc.expectErr != "" {
			require.Error(t, err)
			require.Contains(t, err.Error(), tc.expectErr)
			require.Nil(t, out)
			return
		}

		require.NoError(t, err)
		require.Equal(t, tc.expect, out.Services)
	}
	tcs := []testcase{
		{
			name: "can read all",
			token: tokenWithRules(t, codec, TestDefaultInitialManagementToken,
				`
			service_prefix "" {
				policy = "read"
			}
			`),
			expect: structs.ServiceList{
				structs.NewServiceName("api", nil),
				structs.NewServiceName("web", nil),
			},
		},
		{
			name: "filtered",
			token: tokenWithRules(t, codec, TestDefaultInitialManagementToken,
				`
			service "web" { policy = "read" }
			service "api" { policy = "deny" }
			`),
			expect: structs.ServiceList{
				structs.NewServiceName("web", nil),
			},
		},
		{
			name: "no service rules filters all results",
			token: tokenWithRules(t, codec, TestDefaultInitialManagementToken,
				``),
			expect: structs.ServiceList{},
		},
		{
			name: "no service rules but mesh write shows all results",
			token: tokenWithRules(t, codec, TestDefaultInitialManagementToken,
				`mesh = "write"`),
			expect: structs.ServiceList{
				structs.NewServiceName("api", nil),
				structs.NewServiceName("web", nil),
			},
		},
	}
	for _, tc := range tcs {
		t.Run(tc.name, func(t *testing.T) {
			run(t, tc)
		})
	}
}

func testUUID() string {
	buf := make([]byte, 16)
	if _, err := rand.Read(buf); err != nil {
		panic(fmt.Errorf("failed to read random bytes: %v", err))
	}

	return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x",
		buf[0:4],
		buf[4:6],
		buf[6:8],
		buf[8:10],
		buf[10:16])
}

func TestInternal_AssignManualServiceVIPs(t *testing.T) {
	netutil.GetAgentBindAddrFunc = netutil.GetMockGetAgentBindAddrFunc("0.0.0.0")
	if testing.Short() {
		t.Skip("too slow for testing.Short")
	}
	t.Parallel()

	dir1, s1 := testServer(t)
	defer os.RemoveAll(dir1)
	defer s1.Shutdown()
	codec := rpcClient(t, s1)
	defer codec.Close()

	testrpc.WaitForLeader(t, s1.RPC, "dc1")

	// Set up web service with no manual IPs, and an existing service with manual IPs set.
	registerReq := structs.RegisterRequest{
		Datacenter: "dc1",
		Node:       "foo",
		Address:    "127.0.0.1",
		Service: &structs.NodeService{
			ID:      "web",
			Service: "web",
			Port:    8888,
			Connect: structs.ServiceConnect{Native: true},
		},
	}
	var out struct{}
	require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &registerReq, &out))

	registerReq2 := structs.RegisterRequest{
		Datacenter: "dc1",
		Node:       "foo",
		Address:    "127.0.0.1",
		Service: &structs.NodeService{
			ID:      "existing",
			Service: "existing",
			Port:    9999,
			Connect: structs.ServiceConnect{Native: true},
		},
	}
	require.NoError(t, msgpackrpc.CallWithCodec(codec, "Catalog.Register", &registerReq2, &out))

	req := structs.AssignServiceManualVIPsRequest{
		Service:    "existing",
		ManualVIPs: []string{"8.8.8.8", "9.9.9.9"},
	}
	var resp structs.AssignServiceManualVIPsResponse
	require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.AssignManualServiceVIPs", req, &resp))

	type testcase struct {
		name        string
		req         structs.AssignServiceManualVIPsRequest
		expect      structs.AssignServiceManualVIPsResponse
		expectAgain structs.AssignServiceManualVIPsResponse
		expectErr   string
		expectIPs   []string
	}

	run := func(t *testing.T, tc testcase, again bool) {
		if tc.expectErr != "" && again {
			return // we don't retest known errors
		}

		var resp structs.AssignServiceManualVIPsResponse
		idx1 := s1.raft.CommitIndex()
		err := msgpackrpc.CallWithCodec(codec, "Internal.AssignManualServiceVIPs", tc.req, &resp)
		idx2 := s1.raft.CommitIndex()
		if tc.expectErr != "" {
			testutil.RequireErrorContains(t, err, tc.expectErr)
		} else {
			if again {
				require.Equal(t, tc.expectAgain, resp)
				require.Equal(t, idx1, idx2, "no raft operations occurred")
			} else {
				require.Equal(t, tc.expect, resp)
			}

			psn := structs.PeeredServiceName{ServiceName: structs.NewServiceName(tc.req.Service, nil)}
			got, err := s1.fsm.State().ServiceManualVIPs(psn)
			require.NoError(t, err)
			require.NotNil(t, got)
			require.Equal(t, tc.expectIPs, got.ManualIPs)
		}
	}

	tcs := []testcase{
		{
			name: "successful manual ip assignment",
			req: structs.AssignServiceManualVIPsRequest{
				Service:    "web",
				ManualVIPs: []string{"1.1.1.1", "2.2.2.2"},
			},
			expectIPs:   []string{"1.1.1.1", "2.2.2.2"},
			expect:      structs.AssignServiceManualVIPsResponse{Found: true},
			expectAgain: structs.AssignServiceManualVIPsResponse{Found: true},
		},
		{
			name: "successfully ignoring duplicates",
			req: structs.AssignServiceManualVIPsRequest{
				Service:    "web",
				ManualVIPs: []string{"1.2.3.4", "5.6.7.8", "1.2.3.4", "5.6.7.8"},
			},
			expectIPs:   []string{"1.2.3.4", "5.6.7.8"},
			expect:      structs.AssignServiceManualVIPsResponse{Found: true},
			expectAgain: structs.AssignServiceManualVIPsResponse{Found: true},
		},
		{
			name: "reassign existing ip",
			req: structs.AssignServiceManualVIPsRequest{
				Service:    "web",
				ManualVIPs: []string{"8.8.8.8"},
			},
			expectIPs: []string{"8.8.8.8"},
			expect: structs.AssignServiceManualVIPsResponse{
				Found: true,
				UnassignedFrom: []structs.PeeredServiceName{
					{
						ServiceName: structs.ServiceNameFromString("existing"),
					},
				},
			},
			// When we repeat this operation the second time it's a no-op.
			expectAgain: structs.AssignServiceManualVIPsResponse{Found: true},
		},
		{
			name: "invalid ip",
			req: structs.AssignServiceManualVIPsRequest{
				Service:    "web",
				ManualVIPs: []string{"3.3.3.3", "invalid"},
			},
			expectErr: "not a valid",
		},
	}
	for _, tc := range tcs {
		t.Run(tc.name, func(t *testing.T) {
			t.Run("initial", func(t *testing.T) {
				run(t, tc, false)
			})
			if tc.expectErr == "" {
				t.Run("repeat", func(t *testing.T) {
					run(t, tc, true) // only repeat a write if it isn't an known error
				})
			}
		})
	}
}
