diff --git a/.github/workflows/test-integrations.yml b/.github/workflows/test-integrations.yml index ef89176536..6816d9ef34 100644 --- a/.github/workflows/test-integrations.yml +++ b/.github/workflows/test-integrations.yml @@ -496,6 +496,8 @@ jobs: contents: read strategy: fail-fast: false + env: + DEPLOYER_CONSUL_DATAPLANE_IMAGE: "docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.3-dev" steps: - name: Checkout code uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 @@ -513,20 +515,22 @@ jobs: run: | mkdir -p "${{ env.TEST_RESULTS_DIR }}" export NOLOGBUFFER=1 - cd ./test-integ/connect + cd ./test-integ go run gotest.tools/gotestsum@v${{env.GOTESTSUM_VERSION}} \ --raw-command \ --format=standard-verbose \ --debug \ - --packages="./..." \ -- \ go test \ -tags "${{ env.GOTAGS }}" \ -timeout=20m \ -parallel=2 \ - -json . \ + -json \ + `go list -tags "${{ env.GOTAGS }}" ./... | grep -v peering_commontopo` \ --target-image ${{ env.CONSUL_LATEST_IMAGE_NAME }} \ - --target-version local + --target-version local \ + --latest-image ${{ env.CONSUL_LATEST_IMAGE_NAME }} \ + --latest-version latest env: # this is needed because of incompatibility between RYUK container and GHA GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml diff --git a/test-integ/Makefile b/test-integ/Makefile new file mode 100644 index 0000000000..0fd35d4eda --- /dev/null +++ b/test-integ/Makefile @@ -0,0 +1,42 @@ +SHELL := /bin/bash + +.PHONY: noop +noop: + +##@ Build + +.PHONY: tidy +tidy: ## Run go mod tidy. + go mod tidy + +##@ Checks + +.PHONY: format +format: ## Format the go files. + @for f in $$(find . -name '*.go' -print); do \ + gofmt -s -w $$f ; \ + done + +.PHONY: lint +lint: ## Run the full linting rules. + golangci-lint run -v + +.PHONY: vet +vet: ## Run go vet. + go vet ./... + +##@ Help + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk commands is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) diff --git a/test-integ/catalogv2/explicit_destinations_test.go b/test-integ/catalogv2/explicit_destinations_test.go new file mode 100644 index 0000000000..a62d98b7c6 --- /dev/null +++ b/test-integ/catalogv2/explicit_destinations_test.go @@ -0,0 +1,284 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package catalogv2 + +import ( + "fmt" + "strings" + "testing" + + pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + libassert "github.com/hashicorp/consul/test/integration/consul-container/libs/assert" + "github.com/hashicorp/consul/test/integration/consul-container/libs/utils" + "github.com/hashicorp/consul/testing/deployer/sprawl/sprawltest" + "github.com/hashicorp/consul/testing/deployer/topology" + + "github.com/hashicorp/consul/test-integ/topoutil" +) + +// TestBasicL4ExplicitDestination sets up the following: +// +// - 1 cluster (no peering / no wanfed) +// - 3 servers in that cluster +// - v2 arch is activated +// - for each tenancy, only using v2 constructs: +// - a client with one explicit destination to a single port service +// - a client with multiple explicit destinations to multiple ports of the +// same multiport service +// +// When this test is executed in CE it will only use the default/default +// tenancy. +// +// When this test is executed in Enterprise it will additionally test the same +// things within these tenancies: +// +// - part1/default +// - default/nsa +// - part1/nsa +func TestBasicL4ExplicitDestination(t *testing.T) { + cfg := testBasicL4ExplicitDestinationCreator{}.NewConfig(t) + + sp := sprawltest.Launch(t, cfg) + + var ( + asserter = topoutil.NewAsserter(sp) + + topo = sp.Topology() + cluster = topo.Clusters["dc1"] + + ships = topo.ComputeRelationships() + ) + + clientV2 := sp.ResourceServiceClientForCluster(cluster.Name) + + t.Log(topology.RenderRelationships(ships)) + + // Make sure things are in v2. + for _, name := range []string{ + "single-server", + "single-client", + "multi-server", + "multi-client", + } { + libassert.CatalogV2ServiceHasEndpointCount(t, clientV2, name, nil, 1) + } + + // Check relationships + for _, ship := range ships { + t.Run("relationship: "+ship.String(), func(t *testing.T) { + var ( + svc = ship.Caller + u = ship.Upstream + clusterPrefix string + ) + + if u.Peer == "" { + if u.ID.PartitionOrDefault() == "default" { + clusterPrefix = strings.Join([]string{u.PortName, u.ID.Name, u.ID.Namespace, u.Cluster, "internal"}, ".") + } else { + clusterPrefix = strings.Join([]string{u.PortName, u.ID.Name, u.ID.Namespace, u.ID.Partition, u.Cluster, "internal-v1"}, ".") + } + } else { + clusterPrefix = strings.Join([]string{u.ID.Name, u.ID.Namespace, u.Peer, "external"}, ".") + } + + asserter.UpstreamEndpointStatus(t, svc, clusterPrefix+".", "HEALTHY", 1) + asserter.HTTPServiceEchoes(t, svc, u.LocalPort, "") + asserter.FortioFetch2FortioName(t, svc, u, cluster.Name, u.ID) + }) + } +} + +type testBasicL4ExplicitDestinationCreator struct{} + +func (c testBasicL4ExplicitDestinationCreator) NewConfig(t *testing.T) *topology.Config { + const clusterName = "dc1" + + servers := topoutil.NewTopologyServerSet(clusterName+"-server", 3, []string{clusterName, "wan"}, nil) + + cluster := &topology.Cluster{ + Enterprise: utils.IsEnterprise(), + Name: clusterName, + Nodes: servers, + } + + lastNode := 0 + nodeName := func() string { + lastNode++ + return fmt.Sprintf("%s-box%d", clusterName, lastNode) + } + + c.topologyConfigAddNodes(t, cluster, nodeName, "default", "default") + if cluster.Enterprise { + c.topologyConfigAddNodes(t, cluster, nodeName, "part1", "default") + c.topologyConfigAddNodes(t, cluster, nodeName, "part1", "nsa") + c.topologyConfigAddNodes(t, cluster, nodeName, "default", "nsa") + } + + return &topology.Config{ + Images: topoutil.TargetImages(), + Networks: []*topology.Network{ + {Name: clusterName}, + {Name: "wan", Type: "wan"}, + }, + Clusters: []*topology.Cluster{ + cluster, + }, + } +} + +func (c testBasicL4ExplicitDestinationCreator) topologyConfigAddNodes( + t *testing.T, + cluster *topology.Cluster, + nodeName func() string, + partition, + namespace string, +) { + clusterName := cluster.Name + + newServiceID := func(name string) topology.ServiceID { + return topology.ServiceID{ + Partition: partition, + Namespace: namespace, + Name: name, + } + } + + tenancy := &pbresource.Tenancy{ + Partition: partition, + Namespace: namespace, + PeerName: "local", + } + + singleportServerNode := &topology.Node{ + Kind: topology.NodeKindDataplane, + Version: topology.NodeVersionV2, + Partition: partition, + Name: nodeName(), + Services: []*topology.Service{ + topoutil.NewFortioServiceWithDefaults( + clusterName, + newServiceID("single-server"), + topology.NodeVersionV2, + nil, + ), + }, + } + singleportClientNode := &topology.Node{ + Kind: topology.NodeKindDataplane, + Version: topology.NodeVersionV2, + Partition: partition, + Name: nodeName(), + Services: []*topology.Service{ + topoutil.NewFortioServiceWithDefaults( + clusterName, + newServiceID("single-client"), + topology.NodeVersionV2, + func(svc *topology.Service) { + delete(svc.Ports, "grpc") // v2 mode turns this on, so turn it off + delete(svc.Ports, "http-alt") // v2 mode turns this on, so turn it off + svc.Upstreams = []*topology.Upstream{{ + ID: newServiceID("single-server"), + PortName: "http", + LocalAddress: "0.0.0.0", // needed for an assertion + LocalPort: 5000, + }} + }, + ), + }, + } + singleportTrafficPerms := sprawltest.MustSetResourceData(t, &pbresource.Resource{ + Id: &pbresource.ID{ + Type: pbauth.TrafficPermissionsType, + Name: "single-server-perms", + Tenancy: tenancy, + }, + }, &pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "single-server", + }, + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{{ + Sources: []*pbauth.Source{{ + IdentityName: "single-client", + Namespace: namespace, + }}, + }}, + }) + + multiportServerNode := &topology.Node{ + Kind: topology.NodeKindDataplane, + Version: topology.NodeVersionV2, + Partition: partition, + Name: nodeName(), + Services: []*topology.Service{ + topoutil.NewFortioServiceWithDefaults( + clusterName, + newServiceID("multi-server"), + topology.NodeVersionV2, + nil, + ), + }, + } + multiportClientNode := &topology.Node{ + Kind: topology.NodeKindDataplane, + Version: topology.NodeVersionV2, + Partition: partition, + Name: nodeName(), + Services: []*topology.Service{ + topoutil.NewFortioServiceWithDefaults( + clusterName, + newServiceID("multi-client"), + topology.NodeVersionV2, + func(svc *topology.Service) { + svc.Upstreams = []*topology.Upstream{ + { + ID: newServiceID("multi-server"), + PortName: "http", + LocalAddress: "0.0.0.0", // needed for an assertion + LocalPort: 5000, + }, + { + ID: newServiceID("multi-server"), + PortName: "http-alt", + LocalAddress: "0.0.0.0", // needed for an assertion + LocalPort: 5001, + }, + } + }, + ), + }, + } + multiportTrafficPerms := sprawltest.MustSetResourceData(t, &pbresource.Resource{ + Id: &pbresource.ID{ + Type: pbauth.TrafficPermissionsType, + Name: "multi-server-perms", + Tenancy: tenancy, + }, + }, &pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "multi-server", + }, + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{{ + Sources: []*pbauth.Source{{ + IdentityName: "multi-client", + Namespace: namespace, + }}, + }}, + }) + + cluster.Nodes = append(cluster.Nodes, + singleportClientNode, + singleportServerNode, + multiportClientNode, + multiportServerNode, + ) + + cluster.InitialResources = append(cluster.InitialResources, + singleportTrafficPerms, + multiportTrafficPerms, + ) +} diff --git a/test-integ/go.mod b/test-integ/go.mod index d0154d7a8f..034753efec 100644 --- a/test-integ/go.mod +++ b/test-integ/go.mod @@ -4,6 +4,7 @@ go 1.20 require ( github.com/hashicorp/consul/api v1.24.0 + github.com/hashicorp/consul/proto-public v0.4.1 github.com/hashicorp/consul/sdk v0.14.1 github.com/hashicorp/consul/test/integration/consul-container v0.0.0-20230628201853-bdf4fad7c5a5 github.com/hashicorp/consul/testing/deployer v0.0.0-20230811171106-4a0afb5d1373 @@ -93,8 +94,8 @@ require ( github.com/hashicorp/consul v1.16.1 // indirect github.com/hashicorp/consul-awsauth v0.0.0-20220713182709-05ac1c5c2706 // indirect github.com/hashicorp/consul-net-rpc v0.0.0-20221205195236-156cfab66a69 // indirect + github.com/hashicorp/consul-server-connection-manager v0.1.4 // indirect github.com/hashicorp/consul/envoyextensions v0.4.1 // indirect - github.com/hashicorp/consul/proto-public v0.4.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-bexpr v0.1.2 // indirect github.com/hashicorp/go-connlimit v0.3.0 // indirect @@ -104,6 +105,7 @@ require ( github.com/hashicorp/go-msgpack v1.1.5 // indirect github.com/hashicorp/go-msgpack/v2 v2.0.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-netaddrs v0.1.0 // indirect github.com/hashicorp/go-plugin v1.4.5 // indirect github.com/hashicorp/go-raftchunking v0.7.0 // indirect github.com/hashicorp/go-retryablehttp v0.6.7 // indirect diff --git a/test-integ/go.sum b/test-integ/go.sum index aee362bf63..09543e235c 100644 --- a/test-integ/go.sum +++ b/test-integ/go.sum @@ -395,6 +395,8 @@ github.com/hashicorp/consul-awsauth v0.0.0-20220713182709-05ac1c5c2706 h1:1ZEjnv github.com/hashicorp/consul-awsauth v0.0.0-20220713182709-05ac1c5c2706/go.mod h1:1Cs8FlmD1BfSQXJGcFLSV5FuIx1AbJP+EJGdxosoS2g= github.com/hashicorp/consul-net-rpc v0.0.0-20221205195236-156cfab66a69 h1:wzWurXrxfSyG1PHskIZlfuXlTSCj1Tsyatp9DtaasuY= github.com/hashicorp/consul-net-rpc v0.0.0-20221205195236-156cfab66a69/go.mod h1:svUZZDvotY8zTODknUePc6mZ9pX8nN0ViGwWcUSOBEA= +github.com/hashicorp/consul-server-connection-manager v0.1.4 h1:wrcSRV6WGXFBNpNbN6XsdoGgBOyso7ZbN5VaWPEX1jY= +github.com/hashicorp/consul-server-connection-manager v0.1.4/go.mod h1:LMqHkALoLP0HUQKOG21xXYr0YPUayIQIHNTlmxG100E= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -429,6 +431,8 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-netaddrs v0.1.0 h1:TnlYvODD4C/wO+j7cX1z69kV5gOzI87u3OcUinANaW8= +github.com/hashicorp/go-netaddrs v0.1.0/go.mod h1:33+a/emi5R5dqRspOuZKO0E+Tuz5WV1F84eRWALkedA= github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/go-plugin v1.4.5 h1:oTE/oQR4eghggRg8VY7PAz3dr++VwDNBGCcOfIvHpBo= github.com/hashicorp/go-plugin v1.4.5/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= @@ -1266,8 +1270,6 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= -google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/grpc v1.57.2 h1:uw37EN34aMFFXB2QPW7Tq6tdTbind1GpRxw5aOX3a5k= google.golang.org/grpc v1.57.2/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= diff --git a/test-integ/peering_commontopo/ac7_1_rotate_gw_test.go b/test-integ/peering_commontopo/ac7_1_rotate_gw_test.go index 4973cb6d04..89f1ed04f2 100644 --- a/test-integ/peering_commontopo/ac7_1_rotate_gw_test.go +++ b/test-integ/peering_commontopo/ac7_1_rotate_gw_test.go @@ -8,10 +8,11 @@ import ( "strings" "testing" + "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/testing/deployer/topology" "github.com/stretchr/testify/require" - "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/test-integ/topoutil" ) // TestRotateGW ensures that peered services continue to be able to talk to their @@ -145,7 +146,7 @@ func (s *suiteRotateGW) setup(t *testing.T, ct *commonTopo) { if clu.Datacenter == agentlessDC { nodeKind = topology.NodeKindDataplane } - clu.Nodes = append(clu.Nodes, newTopologyMeshGatewaySet( + clu.Nodes = append(clu.Nodes, topoutil.NewTopologyMeshGatewaySet( nodeKind, "default", s.newMGWNodeName, diff --git a/test-integ/peering_commontopo/commontopo.go b/test-integ/peering_commontopo/commontopo.go index e43456b231..94ada34347 100644 --- a/test-integ/peering_commontopo/commontopo.go +++ b/test-integ/peering_commontopo/commontopo.go @@ -4,23 +4,21 @@ package peering import ( - "bytes" "context" "fmt" - "strconv" "testing" - "text/tabwriter" "time" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/hashicorp/consul/test/integration/consul-container/libs/utils" "github.com/hashicorp/consul/testing/deployer/sprawl" "github.com/hashicorp/consul/testing/deployer/sprawl/sprawltest" "github.com/hashicorp/consul/testing/deployer/topology" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/hashicorp/consul/test/integration/consul-container/libs/utils" + "github.com/hashicorp/consul/test-integ/topoutil" ) // commonTopo helps create a shareable topology configured to represent @@ -46,7 +44,7 @@ type commonTopo struct { // set after Launch. Should be considered read-only Sprawl *sprawl.Sprawl - Assert *asserter + Assert *topoutil.Asserter // track per-DC services to prevent duplicates services map[string]map[topology.ServiceID]struct{} @@ -122,14 +120,14 @@ func (ct *commonTopo) Launch(t *testing.T) { } ct.Sprawl = sprawltest.Launch(t, ct.Cfg) - ct.Assert = newAsserter(ct.Sprawl) + ct.Assert = topoutil.NewAsserter(ct.Sprawl) ct.postLaunchChecks(t) } // tests that use Relaunch might want to call this again afterwards func (ct *commonTopo) postLaunchChecks(t *testing.T) { t.Logf("TESTING RELATIONSHIPS: \n%s", - renderRelationships(computeRelationships(ct.Sprawl.Topology())), + topology.RenderRelationships(ct.Sprawl.Topology().ComputeRelationships()), ) // check that exports line up as expected @@ -317,41 +315,23 @@ func ConfigEntryPartition(p string) string { return p } -// disableNode is a no-op if the node is already disabled. +// DisableNode is a no-op if the node is already disabled. func DisableNode(t *testing.T, cfg *topology.Config, clusterName string, nid topology.NodeID) *topology.Config { - nodes := cfg.Cluster(clusterName).Nodes - var found bool - for _, n := range nodes { - if n.ID() == nid { - found = true - if n.Disabled { - return cfg - } - t.Logf("disabling node %s in cluster %s", nid.String(), clusterName) - n.Disabled = true - break - } + changed, err := cfg.DisableNode(clusterName, nid) + require.NoError(t, err) + if changed { + t.Logf("disabling node %s in cluster %s", nid.String(), clusterName) } - require.True(t, found, "expected to find nodeID %q in cluster %q", nid.String(), clusterName) return cfg } -// enableNode is a no-op if the node is already enabled. +// EnableNode is a no-op if the node is already enabled. func EnableNode(t *testing.T, cfg *topology.Config, clusterName string, nid topology.NodeID) *topology.Config { - nodes := cfg.Cluster(clusterName).Nodes - var found bool - for _, n := range nodes { - if n.ID() == nid { - found = true - if !n.Disabled { - return cfg - } - t.Logf("enabling node %s in cluster %s", nid.String(), clusterName) - n.Disabled = false - break - } + changed, err := cfg.EnableNode(clusterName, nid) + require.NoError(t, err) + if changed { + t.Logf("enabling node %s in cluster %s", nid.String(), clusterName) } - require.True(t, found, "expected to find nodeID %q in cluster %q", nid.String(), clusterName) return cfg } @@ -386,7 +366,7 @@ func addMeshGateways(c *topology.Cluster) { nodeKind = topology.NodeKindDataplane } for _, p := range c.Partitions { - c.Nodes = topology.MergeSlices(c.Nodes, newTopologyMeshGatewaySet( + c.Nodes = topology.MergeSlices(c.Nodes, topoutil.NewTopologyMeshGatewaySet( nodeKind, p.Name, fmt.Sprintf("%s-%s-mgw", c.Name, p.Name), @@ -402,7 +382,7 @@ func clusterWithJustServers(name string, numServers int) *topology.Cluster { Enterprise: utils.IsEnterprise(), Name: name, Datacenter: name, - Nodes: newTopologyServerSet( + Nodes: topoutil.NewTopologyServerSet( name+"-server", numServers, []string{name}, @@ -458,168 +438,11 @@ func injectTenancies(clu *topology.Cluster) { } } -func newTopologyServerSet( - namePrefix string, - num int, - networks []string, - mutateFn func(i int, node *topology.Node), -) []*topology.Node { - var out []*topology.Node - for i := 1; i <= num; i++ { - name := namePrefix + strconv.Itoa(i) - - node := &topology.Node{ - Kind: topology.NodeKindServer, - Name: name, - } - for _, net := range networks { - node.Addresses = append(node.Addresses, &topology.Address{Network: net}) - } - - if mutateFn != nil { - mutateFn(i, node) - } - - out = append(out, node) - } - return out -} - -func newTopologyMeshGatewaySet( - nodeKind topology.NodeKind, - partition string, - namePrefix string, - num int, - networks []string, - mutateFn func(i int, node *topology.Node), -) []*topology.Node { - var out []*topology.Node - for i := 1; i <= num; i++ { - name := namePrefix + strconv.Itoa(i) - - node := &topology.Node{ - Kind: nodeKind, - Partition: partition, - Name: name, - Services: []*topology.Service{{ - ID: topology.ServiceID{Name: "mesh-gateway"}, - Port: 8443, - EnvoyAdminPort: 19000, - IsMeshGateway: true, - }}, - } - for _, net := range networks { - node.Addresses = append(node.Addresses, &topology.Address{Network: net}) - } - - if mutateFn != nil { - mutateFn(i, node) - } - - out = append(out, node) - } - return out -} - -const HashicorpDockerProxy = "docker.mirror.hashicorp.services" - +// Deprecated: topoutil.NewFortioServiceWithDefaults func NewFortioServiceWithDefaults( cluster string, sid topology.ServiceID, mut func(s *topology.Service), ) *topology.Service { - const ( - httpPort = 8080 - grpcPort = 8079 - adminPort = 19000 - ) - sid.Normalize() - - svc := &topology.Service{ - ID: sid, - Image: HashicorpDockerProxy + "/fortio/fortio", - Port: httpPort, - EnvoyAdminPort: adminPort, - CheckTCP: "127.0.0.1:" + strconv.Itoa(httpPort), - Env: []string{ - "FORTIO_NAME=" + cluster + "::" + sid.String(), - }, - Command: []string{ - "server", - "-http-port", strconv.Itoa(httpPort), - "-grpc-port", strconv.Itoa(grpcPort), - "-redirect-port", "-disabled", - }, - } - if mut != nil { - mut(svc) - } - return svc -} - -// computeRelationships will analyze a full topology and generate all of the -// downstream/upstream information for all of them. -func computeRelationships(topo *topology.Topology) []Relationship { - var out []Relationship - for _, cluster := range topo.Clusters { - for _, n := range cluster.Nodes { - for _, s := range n.Services { - for _, u := range s.Upstreams { - out = append(out, Relationship{ - Caller: s, - Upstream: u, - }) - } - } - } - } - return out -} - -// renderRelationships will take the output of ComputeRelationships and display -// it in tabular form. -func renderRelationships(ships []Relationship) string { - var buf bytes.Buffer - w := tabwriter.NewWriter(&buf, 0, 0, 3, ' ', tabwriter.Debug) - fmt.Fprintf(w, "DOWN\tnode\tservice\tport\tUP\tservice\t\n") - for _, r := range ships { - fmt.Fprintf(w, - "%s\t%s\t%s\t%d\t%s\t%s\t\n", - r.downCluster(), - r.Caller.Node.ID().String(), - r.Caller.ID.String(), - r.Upstream.LocalPort, - r.upCluster(), - r.Upstream.ID.String(), - ) - } - fmt.Fprintf(w, "\t\t\t\t\t\t\n") - - w.Flush() - return buf.String() -} - -type Relationship struct { - Caller *topology.Service - Upstream *topology.Upstream -} - -func (r Relationship) String() string { - return fmt.Sprintf( - "%s on %s in %s via :%d => %s in %s", - r.Caller.ID.String(), - r.Caller.Node.ID().String(), - r.downCluster(), - r.Upstream.LocalPort, - r.Upstream.ID.String(), - r.upCluster(), - ) -} - -func (r Relationship) downCluster() string { - return r.Caller.Node.Cluster -} - -func (r Relationship) upCluster() string { - return r.Upstream.Cluster + return topoutil.NewFortioServiceWithDefaults(cluster, sid, topology.NodeVersionV1, mut) } diff --git a/test-integ/peering_commontopo/asserter.go b/test-integ/topoutil/asserter.go similarity index 75% rename from test-integ/peering_commontopo/asserter.go rename to test-integ/topoutil/asserter.go index b5aa71ebae..3795e18794 100644 --- a/test-integ/peering_commontopo/asserter.go +++ b/test-integ/topoutil/asserter.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 -package peering +package topoutil import ( "fmt" @@ -12,18 +12,17 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/testing/deployer/topology" - "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/testutil/retry" libassert "github.com/hashicorp/consul/test/integration/consul-container/libs/assert" "github.com/hashicorp/consul/test/integration/consul-container/libs/utils" + "github.com/hashicorp/consul/testing/deployer/topology" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -// asserter is a utility to help in reducing boilerplate in invoking test +// Asserter is a utility to help in reducing boilerplate in invoking test // assertions against consul-topology Sprawl components. // // The methods should largely take in *topology.Service instances in lieu of @@ -33,32 +32,33 @@ import ( // If it's up to the test (like picking an upstream) leave port as an argument // but still take the service and use that to grab the local ip from the // topology.Node. -type asserter struct { - sp sprawlLite +type Asserter struct { + sp SprawlLite } // *sprawl.Sprawl satisfies this. We don't need anything else. -type sprawlLite interface { +type SprawlLite interface { HTTPClientForCluster(clusterName string) (*http.Client, error) APIClientForNode(clusterName string, nid topology.NodeID, token string) (*api.Client, error) APIClientForCluster(clusterName string, token string) (*api.Client, error) + ResourceServiceClientForCluster(clusterName string) pbresource.ResourceServiceClient Topology() *topology.Topology } -// newAsserter creates a new assertion helper for the provided sprawl. -func newAsserter(sp sprawlLite) *asserter { - return &asserter{ +// NewAsserter creates a new assertion helper for the provided sprawl. +func NewAsserter(sp SprawlLite) *Asserter { + return &Asserter{ sp: sp, } } -func (a *asserter) mustGetHTTPClient(t *testing.T, cluster string) *http.Client { +func (a *Asserter) mustGetHTTPClient(t *testing.T, cluster string) *http.Client { client, err := a.httpClientFor(cluster) require.NoError(t, err) return client } -func (a *asserter) mustGetAPIClient(t *testing.T, cluster string) *api.Client { +func (a *Asserter) mustGetAPIClient(t *testing.T, cluster string) *api.Client { clu := a.sp.Topology().Clusters[cluster] cl, err := a.sp.APIClientForCluster(clu.Name, "") require.NoError(t, err) @@ -70,7 +70,7 @@ func (a *asserter) mustGetAPIClient(t *testing.T, cluster string) *api.Client { // // Use this in methods below to magically pick the right proxied http client // given the home of each node being checked. -func (a *asserter) httpClientFor(cluster string) (*http.Client, error) { +func (a *Asserter) httpClientFor(cluster string) (*http.Client, error) { client, err := a.sp.HTTPClientForCluster(cluster) if err != nil { return nil, err @@ -83,7 +83,7 @@ func (a *asserter) httpClientFor(cluster string) (*http.Client, error) { // Exposes libassert.UpstreamEndpointStatus for use against a Sprawl. // // NOTE: this doesn't take a port b/c you always want to use the envoy admin port. -func (a *asserter) UpstreamEndpointStatus( +func (a *Asserter) UpstreamEndpointStatus( t *testing.T, service *topology.Service, clusterName string, @@ -107,7 +107,7 @@ func (a *asserter) UpstreamEndpointStatus( // Exposes libassert.HTTPServiceEchoes for use against a Sprawl. // // NOTE: this takes a port b/c you may want to reach this via your choice of upstream. -func (a *asserter) HTTPServiceEchoes( +func (a *Asserter) HTTPServiceEchoes( t *testing.T, service *topology.Service, port int, @@ -131,7 +131,7 @@ func (a *asserter) HTTPServiceEchoes( // Exposes libassert.HTTPServiceEchoes for use against a Sprawl. // // NOTE: this takes a port b/c you may want to reach this via your choice of upstream. -func (a *asserter) HTTPServiceEchoesResHeader( +func (a *Asserter) HTTPServiceEchoesResHeader( t *testing.T, service *topology.Service, port int, @@ -149,7 +149,7 @@ func (a *asserter) HTTPServiceEchoesResHeader( libassert.HTTPServiceEchoesResHeaderWithClient(t, client, addr, path, expectedResHeader) } -func (a *asserter) HTTPStatus( +func (a *Asserter) HTTPStatus( t *testing.T, service *topology.Service, port int, @@ -179,7 +179,7 @@ func (a *asserter) HTTPStatus( } // asserts that the service sid in cluster and exported by peer localPeerName is passing health checks, -func (a *asserter) HealthyWithPeer(t *testing.T, cluster string, sid topology.ServiceID, peerName string) { +func (a *Asserter) HealthyWithPeer(t *testing.T, cluster string, sid topology.ServiceID, peerName string) { t.Helper() cl := a.mustGetAPIClient(t, cluster) retry.RunWith(&retry.Timer{Timeout: time.Minute * 1, Wait: time.Millisecond * 500}, t, func(r *retry.R) { @@ -198,7 +198,7 @@ func (a *asserter) HealthyWithPeer(t *testing.T, cluster string, sid topology.Se }) } -func (a *asserter) UpstreamEndpointHealthy(t *testing.T, svc *topology.Service, upstream *topology.Upstream) { +func (a *Asserter) UpstreamEndpointHealthy(t *testing.T, svc *topology.Service, upstream *topology.Upstream) { t.Helper() node := svc.Node ip := node.LocalAddress() @@ -216,68 +216,86 @@ func (a *asserter) UpstreamEndpointHealthy(t *testing.T, svc *topology.Service, ) } +type testingT interface { + require.TestingT + Helper() +} + // does a fortio /fetch2 to the given fortio service, targetting the given upstream. Returns // the body, and response with response.Body already Closed. // // We treat 400, 503, and 504s as retryable errors -func (a *asserter) fortioFetch2Upstream(t *testing.T, fortioSvc *topology.Service, upstream *topology.Upstream, path string) (body []byte, res *http.Response) { +func (a *Asserter) fortioFetch2Upstream( + t testingT, + client *http.Client, + addr string, + upstream *topology.Upstream, + path string, +) (body []byte, res *http.Response) { t.Helper() // TODO: fortioSvc.ID.Normalize()? or should that be up to the caller? - node := fortioSvc.Node - client := a.mustGetHTTPClient(t, node.Cluster) - urlbase := fmt.Sprintf("%s:%d", node.LocalAddress(), fortioSvc.Port) - - url := fmt.Sprintf("http://%s/fortio/fetch2?url=%s", urlbase, + url := fmt.Sprintf("http://%s/fortio/fetch2?url=%s", addr, url.QueryEscape(fmt.Sprintf("http://localhost:%d/%s", upstream.LocalPort, path)), ) req, err := http.NewRequest(http.MethodPost, url, nil) require.NoError(t, err) - retry.RunWith(&retry.Timer{Timeout: 60 * time.Second, Wait: time.Millisecond * 500}, t, func(r *retry.R) { - res, err = client.Do(req) - require.NoError(r, err) - defer res.Body.Close() - // not sure when these happen, suspect it's when the mesh gateway in the peer is not yet ready - require.NotEqual(r, http.StatusServiceUnavailable, res.StatusCode) - require.NotEqual(r, http.StatusGatewayTimeout, res.StatusCode) - // not sure when this happens, suspect it's when envoy hasn't configured the local upstream yet - require.NotEqual(r, http.StatusBadRequest, res.StatusCode) - body, err = io.ReadAll(res.Body) - require.NoError(r, err) - }) + + res, err = client.Do(req) + require.NoError(t, err) + defer res.Body.Close() + // not sure when these happen, suspect it's when the mesh gateway in the peer is not yet ready + require.NotEqual(t, http.StatusServiceUnavailable, res.StatusCode) + require.NotEqual(t, http.StatusGatewayTimeout, res.StatusCode) + // not sure when this happens, suspect it's when envoy hasn't configured the local upstream yet + require.NotEqual(t, http.StatusBadRequest, res.StatusCode) + body, err = io.ReadAll(res.Body) + require.NoError(t, err) return body, res } // uses the /fortio/fetch2 endpoint to do a header echo check against an // upstream fortio -func (a *asserter) FortioFetch2HeaderEcho(t *testing.T, fortioSvc *topology.Service, upstream *topology.Upstream) { +func (a *Asserter) FortioFetch2HeaderEcho(t *testing.T, fortioSvc *topology.Service, upstream *topology.Upstream) { const kPassphrase = "x-passphrase" const passphrase = "hello" path := (fmt.Sprintf("/?header=%s:%s", kPassphrase, passphrase)) + var ( + node = fortioSvc.Node + addr = fmt.Sprintf("%s:%d", node.LocalAddress(), fortioSvc.PortOrDefault("http")) + client = a.mustGetHTTPClient(t, node.Cluster) + ) + retry.RunWith(&retry.Timer{Timeout: 60 * time.Second, Wait: time.Millisecond * 500}, t, func(r *retry.R) { - _, res := a.fortioFetch2Upstream(t, fortioSvc, upstream, path) - require.Equal(t, http.StatusOK, res.StatusCode) + _, res := a.fortioFetch2Upstream(r, client, addr, upstream, path) + require.Equal(r, http.StatusOK, res.StatusCode) v := res.Header.Get(kPassphrase) - require.Equal(t, passphrase, v) + require.Equal(r, passphrase, v) }) } // similar to libassert.AssertFortioName, // uses the /fortio/fetch2 endpoint to hit the debug endpoint on the upstream, // and assert that the FORTIO_NAME == name -func (a *asserter) FortioFetch2FortioName(t *testing.T, fortioSvc *topology.Service, upstream *topology.Upstream, clusterName string, sid topology.ServiceID) { +func (a *Asserter) FortioFetch2FortioName(t *testing.T, fortioSvc *topology.Service, upstream *topology.Upstream, clusterName string, sid topology.ServiceID) { t.Helper() + var ( + node = fortioSvc.Node + addr = fmt.Sprintf("%s:%d", node.LocalAddress(), fortioSvc.PortOrDefault("http")) + client = a.mustGetHTTPClient(t, node.Cluster) + ) + var fortioNameRE = regexp.MustCompile(("\nFORTIO_NAME=(.+)\n")) path := "/debug?env=dump" retry.RunWith(&retry.Timer{Timeout: 60 * time.Second, Wait: time.Millisecond * 500}, t, func(r *retry.R) { - body, res := a.fortioFetch2Upstream(t, fortioSvc, upstream, path) - require.Equal(t, http.StatusOK, res.StatusCode) + body, res := a.fortioFetch2Upstream(r, client, addr, upstream, path) + require.Equal(r, http.StatusOK, res.StatusCode) // TODO: not sure we should retry these? m := fortioNameRE.FindStringSubmatch(string(body)) @@ -289,7 +307,7 @@ func (a *asserter) FortioFetch2FortioName(t *testing.T, fortioSvc *topology.Serv // CatalogServiceExists is the same as libassert.CatalogServiceExists, except that it uses // a proxied API client -func (a *asserter) CatalogServiceExists(t *testing.T, cluster string, svc string, opts *api.QueryOptions) { +func (a *Asserter) CatalogServiceExists(t *testing.T, cluster string, svc string, opts *api.QueryOptions) { t.Helper() cl := a.mustGetAPIClient(t, cluster) libassert.CatalogServiceExists(t, cl, svc, opts) diff --git a/test-integ/topoutil/fixtures.go b/test-integ/topoutil/fixtures.go new file mode 100644 index 0000000000..18742dd2c0 --- /dev/null +++ b/test-integ/topoutil/fixtures.go @@ -0,0 +1,120 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package topoutil + +import ( + "strconv" + + "github.com/hashicorp/consul/testing/deployer/topology" +) + +const HashicorpDockerProxy = "docker.mirror.hashicorp.services" + +func NewFortioServiceWithDefaults( + cluster string, + sid topology.ServiceID, + nodeVersion topology.NodeVersion, + mut func(s *topology.Service), +) *topology.Service { + const ( + httpPort = 8080 + grpcPort = 8079 + adminPort = 19000 + ) + sid.Normalize() + + svc := &topology.Service{ + ID: sid, + Image: HashicorpDockerProxy + "/fortio/fortio", + EnvoyAdminPort: adminPort, + CheckTCP: "127.0.0.1:" + strconv.Itoa(httpPort), + Env: []string{ + "FORTIO_NAME=" + cluster + "::" + sid.String(), + }, + Command: []string{ + "server", + "-http-port", strconv.Itoa(httpPort), + "-grpc-port", strconv.Itoa(grpcPort), + "-redirect-port", "-disabled", + }, + } + + if nodeVersion == topology.NodeVersionV2 { + svc.Ports = map[string]int{ + "http": httpPort, + "http-alt": httpPort, + "grpc": grpcPort, + } + } else { + svc.Port = httpPort + } + + if mut != nil { + mut(svc) + } + return svc +} + +func NewTopologyServerSet( + namePrefix string, + num int, + networks []string, + mutateFn func(i int, node *topology.Node), +) []*topology.Node { + var out []*topology.Node + for i := 1; i <= num; i++ { + name := namePrefix + strconv.Itoa(i) + + node := &topology.Node{ + Kind: topology.NodeKindServer, + Name: name, + } + for _, net := range networks { + node.Addresses = append(node.Addresses, &topology.Address{Network: net}) + } + + if mutateFn != nil { + mutateFn(i, node) + } + + out = append(out, node) + } + return out +} + +func NewTopologyMeshGatewaySet( + nodeKind topology.NodeKind, + partition string, + namePrefix string, + num int, + networks []string, + mutateFn func(i int, node *topology.Node), +) []*topology.Node { + var out []*topology.Node + for i := 1; i <= num; i++ { + name := namePrefix + strconv.Itoa(i) + + node := &topology.Node{ + Kind: nodeKind, + Partition: partition, + Name: name, + Services: []*topology.Service{{ + ID: topology.ServiceID{Name: "mesh-gateway"}, + Port: 8443, + EnvoyAdminPort: 19000, + IsMeshGateway: true, + }}, + } + for _, net := range networks { + node.Addresses = append(node.Addresses, &topology.Address{Network: net}) + } + + if mutateFn != nil { + mutateFn(i, node) + } + + out = append(out, node) + } + return out +} diff --git a/test-integ/topoutil/images.go b/test-integ/topoutil/images.go new file mode 100644 index 0000000000..855ba8c2a9 --- /dev/null +++ b/test-integ/topoutil/images.go @@ -0,0 +1,36 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package topoutil + +import ( + "os" + + "github.com/hashicorp/consul/test/integration/consul-container/libs/utils" + "github.com/hashicorp/consul/testing/deployer/topology" +) + +func TargetImages() topology.Images { + // Start with no preferences. + var images topology.Images + if !runningInCI() { + // Until 1.17 GAs, we want the pre-release versions for these tests, + // run outside of CI for convenience. + images = topology.Images{ + ConsulCE: HashicorpDockerProxy + "/hashicorppreview/consul:1.17-dev", + ConsulEnterprise: HashicorpDockerProxy + "/hashicorppreview/consul-enterprise:1.17-dev", + Dataplane: HashicorpDockerProxy + "/hashicorppreview/consul-dataplane:1.3-dev", + } + } + + // We want the image overridden by the local build produced by + // 'make test-compat-integ-setup' or 'make dev-docker'. + testImages := utils.TargetImages() + images = images.OverrideWith(testImages) + + return images +} + +func runningInCI() bool { + return os.Getenv("GITHUB_ACTIONS") != "" || os.Getenv("CI") != "" +} diff --git a/test/integration/consul-container/go.mod b/test/integration/consul-container/go.mod index a74881e46a..0b471b264d 100644 --- a/test/integration/consul-container/go.mod +++ b/test/integration/consul-container/go.mod @@ -31,7 +31,6 @@ require ( github.com/testcontainers/testcontainers-go v0.22.0 golang.org/x/mod v0.12.0 google.golang.org/grpc v1.57.2 - k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 ) require ( @@ -105,6 +104,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 // indirect github.com/hashicorp/consul-awsauth v0.0.0-20220713182709-05ac1c5c2706 // indirect github.com/hashicorp/consul-net-rpc v0.0.0-20221205195236-156cfab66a69 // indirect + github.com/hashicorp/consul-server-connection-manager v0.1.4 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-bexpr v0.1.2 // indirect github.com/hashicorp/go-connlimit v0.3.0 // indirect @@ -113,6 +113,7 @@ require ( github.com/hashicorp/go-memdb v1.3.4 // indirect github.com/hashicorp/go-msgpack v1.1.5 // indirect github.com/hashicorp/go-msgpack/v2 v2.0.0 // indirect + github.com/hashicorp/go-netaddrs v0.1.0 // indirect github.com/hashicorp/go-plugin v1.4.5 // indirect github.com/hashicorp/go-raftchunking v0.7.0 // indirect github.com/hashicorp/go-retryablehttp v0.6.7 // indirect @@ -225,6 +226,7 @@ require ( k8s.io/client-go v0.26.2 // indirect k8s.io/klog/v2 v2.90.1 // indirect k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect + k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/test/integration/consul-container/go.sum b/test/integration/consul-container/go.sum index dfcf46c614..1cbb9315f5 100644 --- a/test/integration/consul-container/go.sum +++ b/test/integration/consul-container/go.sum @@ -391,6 +391,8 @@ github.com/hashicorp/consul-awsauth v0.0.0-20220713182709-05ac1c5c2706 h1:1ZEjnv github.com/hashicorp/consul-awsauth v0.0.0-20220713182709-05ac1c5c2706/go.mod h1:1Cs8FlmD1BfSQXJGcFLSV5FuIx1AbJP+EJGdxosoS2g= github.com/hashicorp/consul-net-rpc v0.0.0-20221205195236-156cfab66a69 h1:wzWurXrxfSyG1PHskIZlfuXlTSCj1Tsyatp9DtaasuY= github.com/hashicorp/consul-net-rpc v0.0.0-20221205195236-156cfab66a69/go.mod h1:svUZZDvotY8zTODknUePc6mZ9pX8nN0ViGwWcUSOBEA= +github.com/hashicorp/consul-server-connection-manager v0.1.4 h1:wrcSRV6WGXFBNpNbN6XsdoGgBOyso7ZbN5VaWPEX1jY= +github.com/hashicorp/consul-server-connection-manager v0.1.4/go.mod h1:LMqHkALoLP0HUQKOG21xXYr0YPUayIQIHNTlmxG100E= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -425,6 +427,8 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-netaddrs v0.1.0 h1:TnlYvODD4C/wO+j7cX1z69kV5gOzI87u3OcUinANaW8= +github.com/hashicorp/go-netaddrs v0.1.0/go.mod h1:33+a/emi5R5dqRspOuZKO0E+Tuz5WV1F84eRWALkedA= github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/go-plugin v1.4.5 h1:oTE/oQR4eghggRg8VY7PAz3dr++VwDNBGCcOfIvHpBo= github.com/hashicorp/go-plugin v1.4.5/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= diff --git a/test/integration/consul-container/libs/assert/envoy.go b/test/integration/consul-container/libs/assert/envoy.go index 760b1f87bc..35f9873741 100644 --- a/test/integration/consul-container/libs/assert/envoy.go +++ b/test/integration/consul-container/libs/assert/envoy.go @@ -75,6 +75,7 @@ func GetEnvoyListenerTCPFiltersWithClient( // AssertUpstreamEndpointStatus validates that proxy was configured with provided clusterName in the healthStatus func AssertUpstreamEndpointStatus(t *testing.T, adminPort int, clusterName, healthStatus string, count int) { + t.Helper() require.True(t, adminPort > 0) AssertUpstreamEndpointStatusWithClient( t, @@ -94,6 +95,7 @@ func AssertUpstreamEndpointStatusWithClient( healthStatus string, count int, ) { + t.Helper() require.NotNil(t, client) require.NotEmpty(t, addr) failer := func() *retry.Timer { @@ -126,6 +128,7 @@ func AssertUpstreamEndpointStatusWithClient( // AssertEnvoyMetricAtMost assert the filered metric by prefix and metric is >= count func AssertEnvoyMetricAtMost(t *testing.T, adminPort int, prefix, metric string, count int) { + t.Helper() var ( stats string err error diff --git a/test/integration/consul-container/libs/assert/service.go b/test/integration/consul-container/libs/assert/service.go index 35fad6bfb1..72e8ef06e7 100644 --- a/test/integration/consul-container/libs/assert/service.go +++ b/test/integration/consul-container/libs/assert/service.go @@ -12,12 +12,16 @@ import ( "testing" "time" + "github.com/hashicorp/consul/api" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/consul/sdk/testutil" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/hashicorp/consul/testing/deployer/util" "github.com/hashicorp/go-cleanhttp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" libservice "github.com/hashicorp/consul/test/integration/consul-container/libs/service" ) @@ -26,6 +30,65 @@ const ( defaultHTTPWait = defaultWait ) +// CatalogV2ServiceExists verifies the service name exists in the Consul catalog +func CatalogV2ServiceExists(t *testing.T, client pbresource.ResourceServiceClient, svc string, tenancy *pbresource.Tenancy) { + t.Helper() + CatalogV2ServiceHasEndpointCount(t, client, svc, tenancy, -1) +} + +// CatalogV2ServiceDoesNotExist verifies the service name does not exist in the Consul catalog +func CatalogV2ServiceDoesNotExist(t *testing.T, client pbresource.ResourceServiceClient, svc string, tenancy *pbresource.Tenancy) { + t.Helper() + ctx := testutil.TestContext(t) + retry.Run(t, func(r *retry.R) { + got, err := util.GetDecodedResource[*pbcatalog.Service](ctx, client, &pbresource.ID{ + Type: pbcatalog.ServiceType, + Name: svc, + Tenancy: tenancy, + }) + require.NoError(r, err, "error reading service data") + require.Nil(r, got, "unexpectedly found Service resource for %q", svc) + + got2, err := util.GetDecodedResource[*pbcatalog.ServiceEndpoints](ctx, client, &pbresource.ID{ + Type: pbcatalog.ServiceEndpointsType, + Name: svc, + Tenancy: tenancy, + }) + require.NotNil(r, err, "error reading service data") + require.Nil(r, got2, "unexpectedly found ServiceEndpoints resource for %q", svc) + }) +} + +// CatalogV2ServiceHasEndpointCount verifies the service name exists in the Consul catalog and has the specified +// number of workload endpoints. +func CatalogV2ServiceHasEndpointCount(t *testing.T, client pbresource.ResourceServiceClient, svc string, tenancy *pbresource.Tenancy, count int) { + t.Helper() + require.False(t, count == 0) + + ctx := testutil.TestContext(t) + retry.Run(t, func(r *retry.R) { + got, err := util.GetDecodedResource[*pbcatalog.Service](ctx, client, &pbresource.ID{ + Type: pbcatalog.ServiceType, + Name: svc, + Tenancy: tenancy, + }) + require.NoError(r, err, "error reading service data") + require.NotNil(r, got, "did not find Service resource for %q", svc) + + got2, err := util.GetDecodedResource[*pbcatalog.ServiceEndpoints](ctx, client, &pbresource.ID{ + Type: pbcatalog.ServiceEndpointsType, + Name: svc, + Tenancy: tenancy, + }) + require.NoError(r, err, "error reading service data") + require.NotNil(r, got2, "did not find ServiceEndpoints resource for %q", svc) + require.NotEmpty(r, got2.Data.Endpoints, "did not find any workload data in the ServiceEndpoints resource for %q", svc) + if count > 0 { + require.Len(r, got2.Data.Endpoints, count) + } + }) +} + // CatalogServiceExists verifies the service name exists in the Consul catalog func CatalogServiceExists(t *testing.T, c *api.Client, svc string, opts *api.QueryOptions) { retry.Run(t, func(r *retry.R) { @@ -39,6 +102,15 @@ func CatalogServiceExists(t *testing.T, c *api.Client, svc string, opts *api.Que }) } +// CatalogServiceDoesNotExist verifies the service name does not exist in the Consul catalog +func CatalogServiceDoesNotExist(t *testing.T, c *api.Client, svc string, opts *api.QueryOptions) { + retry.Run(t, func(r *retry.R) { + services, _, err := c.Catalog().Service(svc, "", opts) + require.NoError(r, err, "error reading service data") + require.Empty(r, services) + }) +} + // CatalogServiceHasInstanceCount verifies the service name exists in the Consul catalog and has the specified // number of instances. func CatalogServiceHasInstanceCount(t *testing.T, c *api.Client, svc string, count int, opts *api.QueryOptions) { @@ -66,6 +138,17 @@ func CatalogNodeExists(t *testing.T, c *api.Client, nodeName string) { }) } +// CatalogNodeDoesNotExist verifies the node name does not exist in the Consul catalog +func CatalogNodeDoesNotExist(t *testing.T, c *api.Client, nodeName string) { + retry.Run(t, func(r *retry.R) { + node, _, err := c.Catalog().Node(nodeName, nil) + if err != nil { + r.Fatal("error reading node data") + } + require.Nil(r, node) + }) +} + // CatalogServiceIsHealthy verifies the service name exists and all instances pass healthchecks func CatalogServiceIsHealthy(t *testing.T, c *api.Client, svc string, opts *api.QueryOptions) { CatalogServiceExists(t, c, svc, opts) @@ -105,6 +188,7 @@ func HTTPServiceEchoesWithClient(t *testing.T, client *http.Client, addr string, func HTTPServiceEchoesResHeader(t *testing.T, ip string, port int, path string, expectedResHeader map[string]string) { doHTTPServiceEchoes(t, ip, port, path, nil, expectedResHeader) } + func HTTPServiceEchoesResHeaderWithClient(t *testing.T, client *http.Client, addr string, path string, expectedResHeader map[string]string) { doHTTPServiceEchoesWithClient(t, client, addr, path, nil, expectedResHeader) } @@ -142,7 +226,7 @@ func doHTTPServiceEchoesWithClient( reader := strings.NewReader(phrase) req, err := http.NewRequest("POST", url, reader) - require.NoError(t, err, "could not construct request") + require.NoError(r, err, "could not construct request") for k, v := range requestHeaders { req.Header.Add(k, v) diff --git a/test/integration/consul-container/libs/utils/version.go b/test/integration/consul-container/libs/utils/version.go index 24e66a8698..1f62306f44 100644 --- a/test/integration/consul-container/libs/utils/version.go +++ b/test/integration/consul-container/libs/utils/version.go @@ -5,6 +5,7 @@ package utils import ( "flag" + "os" "strings" "github.com/hashicorp/consul/testing/deployer/topology" @@ -59,15 +60,18 @@ func GetLatestImageName() string { func TargetImages() topology.Images { img := DockerImage(targetImageName, TargetVersion) + var set topology.Images if IsEnterprise() { - return topology.Images{ - ConsulEnterprise: img, - } + set.ConsulEnterprise = img } else { - return topology.Images{ - ConsulCE: img, - } + set.ConsulCE = img } + + if cdp := os.Getenv("DEPLOYER_CONSUL_DATAPLANE_IMAGE"); cdp != "" { + set.Dataplane = cdp + } + + return set } func IsEnterprise() bool { return isInEnterpriseRepo } diff --git a/testing/deployer/go.mod b/testing/deployer/go.mod index 4b139b5edf..f83eaac076 100644 --- a/testing/deployer/go.mod +++ b/testing/deployer/go.mod @@ -4,45 +4,64 @@ go 1.20 require ( github.com/google/go-cmp v0.5.9 + github.com/hashicorp/consul-server-connection-manager v0.1.4 github.com/hashicorp/consul/api v1.24.0 + github.com/hashicorp/consul/proto-public v0.1.0 github.com/hashicorp/consul/sdk v0.14.1 github.com/hashicorp/go-cleanhttp v0.5.2 github.com/hashicorp/go-hclog v1.5.0 github.com/hashicorp/go-multierror v1.1.1 + github.com/hashicorp/go-rootcerts v1.0.2 github.com/hashicorp/hcl/v2 v2.16.2 github.com/mitchellh/copystructure v1.2.0 github.com/rboyer/safeio v0.2.2 github.com/stretchr/testify v1.8.3 golang.org/x/crypto v0.14.0 + golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 + google.golang.org/grpc v1.56.3 + google.golang.org/protobuf v1.31.0 ) require ( github.com/agext/levenshtein v1.2.1 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.1.3 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fatih/color v1.14.1 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-netaddrs v0.1.0 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.2.1 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/serf v0.10.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-wordwrap v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.11.1 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.26.0 // indirect + github.com/prometheus/procfs v0.6.0 // indirect github.com/zclconf/go-cty v1.12.1 // indirect - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/hashicorp/consul/sdk => ../../sdk +replace ( + github.com/hashicorp/consul/api => ../../api + github.com/hashicorp/consul/proto-public => ../../proto-public + github.com/hashicorp/consul/sdk => ../../sdk +) diff --git a/testing/deployer/go.sum b/testing/deployer/go.sum index 1db52e2ddb..e632e8be79 100644 --- a/testing/deployer/go.sum +++ b/testing/deployer/go.sum @@ -1,3 +1,4 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= @@ -5,6 +6,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -15,9 +17,14 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= +github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -31,23 +38,38 @@ github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/hashicorp/consul/api v1.24.0 h1:u2XyStA2j0jnCiVUU7Qyrt8idjRn4ORhK6DlvZ3bWhA= -github.com/hashicorp/consul/api v1.24.0/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= +github.com/hashicorp/consul-server-connection-manager v0.1.4 h1:wrcSRV6WGXFBNpNbN6XsdoGgBOyso7ZbN5VaWPEX1jY= +github.com/hashicorp/consul-server-connection-manager v0.1.4/go.mod h1:LMqHkALoLP0HUQKOG21xXYr0YPUayIQIHNTlmxG100E= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -65,6 +87,8 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-netaddrs v0.1.0 h1:TnlYvODD4C/wO+j7cX1z69kV5gOzI87u3OcUinANaW8= +github.com/hashicorp/go-netaddrs v0.1.0/go.mod h1:33+a/emi5R5dqRspOuZKO0E+Tuz5WV1F84eRWALkedA= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= @@ -88,10 +112,15 @@ github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -114,6 +143,7 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= @@ -135,6 +165,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -150,14 +181,24 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rboyer/safeio v0.2.2 h1:XhtqyUTRleMYGyBt3ni4j2BtEh669U2ry2INnnd+B4k= github.com/rboyer/safeio v0.2.2/go.mod h1:pSnr2LFXyn/c/fotxotyOdYy7pP/XSh6MpBmzXPjiNc= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= @@ -167,6 +208,7 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= @@ -182,39 +224,52 @@ github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeW golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -233,6 +288,21 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -241,5 +311,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/testing/deployer/sprawl/acl_rules.go b/testing/deployer/sprawl/acl_rules.go index 7c360d85f6..98910126b3 100644 --- a/testing/deployer/sprawl/acl_rules.go +++ b/testing/deployer/sprawl/acl_rules.go @@ -93,6 +93,13 @@ func tokenForService(svc *topology.Service, overridePolicy *api.ACLPolicy, enter } if overridePolicy != nil { token.Policies = []*api.ACLTokenPolicyLink{{ID: overridePolicy.ID}} + } else if svc.IsV2() { + token.TemplatedPolicies = []*api.ACLTemplatedPolicy{{ + TemplateName: api.ACLTemplatedPolicyWorkloadIdentityName, + TemplateVariables: &api.ACLTemplatedPolicyVariables{ + Name: svc.ID.Name, + }, + }} } else { token.ServiceIdentities = []*api.ACLServiceIdentity{{ ServiceName: svc.ID.Name, diff --git a/testing/deployer/sprawl/boot.go b/testing/deployer/sprawl/boot.go index 37656ca0ad..3bbb9e864e 100644 --- a/testing/deployer/sprawl/boot.go +++ b/testing/deployer/sprawl/boot.go @@ -32,9 +32,11 @@ const ( func (s *Sprawl) launch() error { return s.launchType(true) } + func (s *Sprawl) relaunch() error { return s.launchType(false) } + func (s *Sprawl) launchType(firstTime bool) (launchErr error) { if err := build.DockerImages(s.logger, s.runner, s.topology); err != nil { return fmt.Errorf("build.DockerImages: %w", err) @@ -235,6 +237,14 @@ func (s *Sprawl) initConsulServers() error { return fmt.Errorf("error creating final client for cluster=%s: %v", cluster.Name, err) } + // Connect to gRPC as well. + if cluster.EnableV2 { + s.grpcConns[cluster.Name], s.grpcConnCancel[cluster.Name], err = s.dialServerGRPC(cluster, node, mgmtToken) + if err != nil { + return fmt.Errorf("error creating gRPC client conn for cluster=%s: %w", cluster.Name, err) + } + } + // For some reason the grpc resolver stuff for partitions takes some // time to get ready. s.waitForLocalWrites(cluster, mgmtToken) @@ -250,6 +260,10 @@ func (s *Sprawl) initConsulServers() error { return fmt.Errorf("populateInitialConfigEntries[%s]: %w", cluster.Name, err) } + if err := s.populateInitialResources(cluster); err != nil { + return fmt.Errorf("populateInitialResources[%s]: %w", cluster.Name, err) + } + if err := s.createAnonymousToken(cluster); err != nil { return fmt.Errorf("createAnonymousToken[%s]: %w", cluster.Name, err) } @@ -457,6 +471,9 @@ func (s *Sprawl) waitForLocalWrites(cluster *topology.Cluster, token string) { } func (s *Sprawl) waitForClientAntiEntropyOnce(cluster *topology.Cluster) error { + if cluster.EnableV2 { + return nil // v1 catalog is disabled when v2 catalog is enabled + } var ( client = s.clients[cluster.Name] logger = s.logger.With("cluster", cluster.Name) diff --git a/testing/deployer/sprawl/catalog.go b/testing/deployer/sprawl/catalog.go index fc5d2dbc39..75a4fb6fd6 100644 --- a/testing/deployer/sprawl/catalog.go +++ b/testing/deployer/sprawl/catalog.go @@ -4,11 +4,18 @@ package sprawl import ( + "context" "fmt" "net/http" "time" "github.com/hashicorp/consul/api" + pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" "github.com/hashicorp/consul/testing/deployer/topology" "github.com/hashicorp/consul/testing/deployer/util" @@ -41,6 +48,9 @@ func (s *Sprawl) registerServicesToAgents(cluster *topology.Cluster) error { if !node.IsAgent() { continue } + if node.IsV2() { + panic("don't call this") + } agentClient, err := util.ProxyAPIClient( node.LocalProxyPort(), @@ -71,6 +81,9 @@ func (s *Sprawl) registerAgentService( if !node.IsAgent() { panic("called wrong method type") } + if node.IsV2() { + panic("don't call this") + } if svc.IsMeshGateway { return nil // handled at startup time for agent-full, but won't be for agent-less @@ -164,6 +177,8 @@ RETRY: } func (s *Sprawl) registerServicesForDataplaneInstances(cluster *topology.Cluster) error { + identityInfo := make(map[topology.ServiceID]*Resource[*pbauth.WorkloadIdentity]) + for _, node := range cluster.Nodes { if !node.RunsWorkloads() || len(node.Services) == 0 || node.Disabled { continue @@ -178,13 +193,87 @@ func (s *Sprawl) registerServicesForDataplaneInstances(cluster *topology.Cluster } for _, svc := range node.Services { - if err := s.registerCatalogService(cluster, node, svc); err != nil { - return fmt.Errorf("error registering service: %w", err) - } - if !svc.DisableServiceMesh { - if err := s.registerCatalogSidecarService(cluster, node, svc); err != nil { - return fmt.Errorf("error registering sidecar service: %w", err) + if node.IsV2() { + pending := serviceInstanceToResources(node, svc) + + if _, ok := identityInfo[svc.ID]; !ok { + identityInfo[svc.ID] = pending.WorkloadIdentity } + + // Write workload + res, err := pending.Workload.Build() + if err != nil { + return fmt.Errorf("error serializing resource %s: %w", util.IDToString(pending.Workload.Resource.Id), err) + } + workload, err := s.writeResource(cluster, res) + if err != nil { + return err + } + // Write check linked to workload + for _, check := range pending.HealthStatuses { + check.Resource.Owner = workload.Id + res, err := check.Build() + if err != nil { + return fmt.Errorf("error serializing resource %s: %w", util.IDToString(check.Resource.Id), err) + } + if _, err := s.writeResource(cluster, res); err != nil { + return err + } + } + // maybe write destinations + if pending.Destinations != nil { + res, err := pending.Destinations.Build() + if err != nil { + return fmt.Errorf("error serializing resource %s: %w", util.IDToString(pending.Destinations.Resource.Id), err) + } + if _, err := s.writeResource(cluster, res); err != nil { + return err + } + } + } else { + if err := s.registerCatalogServiceV1(cluster, node, svc); err != nil { + return fmt.Errorf("error registering service: %w", err) + } + if !svc.DisableServiceMesh { + if err := s.registerCatalogSidecarServiceV1(cluster, node, svc); err != nil { + return fmt.Errorf("error registering sidecar service: %w", err) + } + } + } + } + } + + for _, identity := range identityInfo { + res, err := identity.Build() + if err != nil { + return fmt.Errorf("error serializing resource %s: %w", util.IDToString(identity.Resource.Id), err) + } + if _, err := s.writeResource(cluster, res); err != nil { + return err + } + } + + if cluster.EnableV2 { + for id, svcData := range cluster.Services { + svcInfo := &Resource[*pbcatalog.Service]{ + Resource: &pbresource.Resource{ + Id: &pbresource.ID{ + Type: pbcatalog.ServiceType, + Name: id.Name, + Tenancy: &pbresource.Tenancy{ + Partition: id.Partition, + Namespace: id.Namespace, + }, + }, + }, + Data: svcData, + } + res, err := svcInfo.Build() + if err != nil { + return fmt.Errorf("error serializing resource %s: %w", util.IDToString(svcInfo.Resource.Id), err) + } + if _, err := s.writeResource(cluster, res); err != nil { + return err } } } @@ -195,6 +284,77 @@ func (s *Sprawl) registerServicesForDataplaneInstances(cluster *topology.Cluster func (s *Sprawl) registerCatalogNode( cluster *topology.Cluster, node *topology.Node, +) error { + if node.IsV2() { + return s.registerCatalogNodeV2(cluster, node) + } + return s.registerCatalogNodeV1(cluster, node) +} + +func (s *Sprawl) registerCatalogNodeV2( + cluster *topology.Cluster, + node *topology.Node, +) error { + if !node.IsDataplane() { + panic("called wrong method type") + } + + nodeRes := &Resource[*pbcatalog.Node]{ + Resource: &pbresource.Resource{ + Id: &pbresource.ID{ + Type: pbcatalog.NodeType, + Name: node.PodName(), + Tenancy: &pbresource.Tenancy{ + Partition: node.Partition, + Namespace: "default", // temporary requirement + }, + }, + Metadata: map[string]string{ + "dataplane-faux": "1", + }, + }, + Data: &pbcatalog.Node{ + Addresses: []*pbcatalog.NodeAddress{ + {Host: node.LocalAddress()}, + }, + }, + } + + res, err := nodeRes.Build() + if err != nil { + return err + } + + _, err = s.writeResource(cluster, res) + return err +} + +func (s *Sprawl) writeResource(cluster *topology.Cluster, res *pbresource.Resource) (*pbresource.Resource, error) { + var ( + client = s.getResourceClient(cluster.Name) + logger = s.logger.With("cluster", cluster.Name) + ) + + ctx := s.getManagementTokenContext(context.Background(), cluster.Name) +RETRY: + wrote, err := client.Write(ctx, &pbresource.WriteRequest{ + Resource: res, + }) + if err != nil { + if isACLNotFound(err) { // TODO: is this right for v2? + time.Sleep(50 * time.Millisecond) + goto RETRY + } + return nil, fmt.Errorf("error creating resource %s: %w", util.IDToString(res.Id), err) + } + + logger.Info("resource upserted", "id", util.IDToString(res.Id)) + return wrote.Resource, nil +} + +func (s *Sprawl) registerCatalogNodeV1( + cluster *topology.Cluster, + node *topology.Node, ) error { if !node.IsDataplane() { panic("called wrong method type") @@ -233,7 +393,7 @@ RETRY: return nil } -func (s *Sprawl) registerCatalogService( +func (s *Sprawl) registerCatalogServiceV1( cluster *topology.Cluster, node *topology.Node, svc *topology.Service, @@ -241,6 +401,9 @@ func (s *Sprawl) registerCatalogService( if !node.IsDataplane() { panic("called wrong method type") } + if node.IsV2() { + panic("don't call this") + } var ( client = s.clients[cluster.Name] @@ -266,7 +429,7 @@ RETRY: return nil } -func (s *Sprawl) registerCatalogSidecarService( +func (s *Sprawl) registerCatalogSidecarServiceV1( cluster *topology.Cluster, node *topology.Node, svc *topology.Service, @@ -277,6 +440,9 @@ func (s *Sprawl) registerCatalogSidecarService( if svc.DisableServiceMesh { panic("not valid") } + if node.IsV2() { + panic("don't call this") + } var ( client = s.clients[cluster.Name] @@ -301,11 +467,172 @@ RETRY: return nil } +type Resource[V proto.Message] struct { + Resource *pbresource.Resource + Data V +} + +func (r *Resource[V]) Build() (*pbresource.Resource, error) { + anyData, err := anypb.New(r.Data) + if err != nil { + return nil, err + } + r.Resource.Data = anyData + return r.Resource, nil +} + +type ServiceResources struct { + Workload *Resource[*pbcatalog.Workload] + HealthStatuses []*Resource[*pbcatalog.HealthStatus] + Destinations *Resource[*pbmesh.Destinations] + WorkloadIdentity *Resource[*pbauth.WorkloadIdentity] +} + +func serviceInstanceToResources( + node *topology.Node, + svc *topology.Service, +) *ServiceResources { + if svc.IsMeshGateway { + panic("v2 does not yet support mesh gateways") + } + + tenancy := &pbresource.Tenancy{ + Partition: svc.ID.Partition, + Namespace: svc.ID.Namespace, + } + + var ( + wlPorts = map[string]*pbcatalog.WorkloadPort{} + ) + for name, port := range svc.Ports { + wlPorts[name] = &pbcatalog.WorkloadPort{ + Port: uint32(port), + Protocol: pbcatalog.Protocol_PROTOCOL_TCP, + } + } + + var ( + selector = &pbcatalog.WorkloadSelector{ + Names: []string{svc.Workload}, + } + + workloadRes = &Resource[*pbcatalog.Workload]{ + Resource: &pbresource.Resource{ + Id: &pbresource.ID{ + Type: pbcatalog.WorkloadType, + Name: svc.Workload, + Tenancy: tenancy, + }, + Metadata: svc.Meta, + }, + Data: &pbcatalog.Workload{ + NodeName: node.PodName(), + Identity: svc.ID.Name, + Ports: wlPorts, + Addresses: []*pbcatalog.WorkloadAddress{ + {Host: node.LocalAddress()}, + }, + }, + } + + worloadIdentityRes = &Resource[*pbauth.WorkloadIdentity]{ + Resource: &pbresource.Resource{ + Id: &pbresource.ID{ + Type: pbauth.WorkloadIdentityType, + Name: svc.ID.Name, + Tenancy: tenancy, + }, + Metadata: svc.Meta, + }, + Data: &pbauth.WorkloadIdentity{}, + } + + healthResList []*Resource[*pbcatalog.HealthStatus] + destinationsRes *Resource[*pbmesh.Destinations] + ) + + if svc.HasCheck() { + // TODO: needs ownerId + checkRes := &Resource[*pbcatalog.HealthStatus]{ + Resource: &pbresource.Resource{ + Id: &pbresource.ID{ + Type: pbcatalog.HealthStatusType, + Name: svc.Workload + "-check-0", + Tenancy: tenancy, + }, + }, + Data: &pbcatalog.HealthStatus{ + Type: "external-sync", + Status: pbcatalog.Health_HEALTH_PASSING, + }, + } + + healthResList = []*Resource[*pbcatalog.HealthStatus]{checkRes} + } + + if node.HasPublicAddress() { + workloadRes.Data.Addresses = append(workloadRes.Data.Addresses, + &pbcatalog.WorkloadAddress{Host: node.PublicAddress(), External: true}, + ) + } + + if !svc.DisableServiceMesh { + workloadRes.Data.Ports["mesh"] = &pbcatalog.WorkloadPort{ + Port: uint32(svc.EnvoyPublicListenerPort), + Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + } + + destinationsRes = &Resource[*pbmesh.Destinations]{ + Resource: &pbresource.Resource{ + Id: &pbresource.ID{ + Type: pbmesh.DestinationsType, + Name: svc.Workload, + Tenancy: tenancy, + }, + }, + Data: &pbmesh.Destinations{ + Workloads: selector, + }, + } + + for _, u := range svc.Upstreams { + dest := &pbmesh.Destination{ + DestinationRef: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Name: u.ID.Name, + Tenancy: &pbresource.Tenancy{ + Partition: u.ID.Partition, + Namespace: u.ID.Namespace, + }, + }, + DestinationPort: u.PortName, + ListenAddr: &pbmesh.Destination_IpPort{ + IpPort: &pbmesh.IPPortAddress{ + Ip: u.LocalAddress, + Port: uint32(u.LocalPort), + }, + }, + } + destinationsRes.Data.Destinations = append(destinationsRes.Data.Destinations, dest) + } + } + + return &ServiceResources{ + Workload: workloadRes, + HealthStatuses: healthResList, + Destinations: destinationsRes, + WorkloadIdentity: worloadIdentityRes, + } +} + func serviceToCatalogRegistration( cluster *topology.Cluster, node *topology.Node, svc *topology.Service, ) *api.CatalogRegistration { + if node.IsV2() { + panic("don't call this") + } reg := &api.CatalogRegistration{ Node: node.PodName(), SkipNodeUpdate: true, @@ -394,6 +721,9 @@ func serviceToSidecarCatalogRegistration( node *topology.Node, svc *topology.Service, ) (topology.ServiceID, *api.CatalogRegistration) { + if node.IsV2() { + panic("don't call this") + } pid := svc.ID pid.Name += "-sidecar-proxy" reg := &api.CatalogRegistration{ diff --git a/testing/deployer/sprawl/consul.go b/testing/deployer/sprawl/consul.go index 2693135784..205fb0235f 100644 --- a/testing/deployer/sprawl/consul.go +++ b/testing/deployer/sprawl/consul.go @@ -4,17 +4,29 @@ package sprawl import ( + "context" "errors" "fmt" "time" "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/testing/deployer/sprawl/internal/secrets" "github.com/hashicorp/consul/testing/deployer/topology" "github.com/hashicorp/consul/testing/deployer/util" ) +func (s *Sprawl) getResourceClient(clusterName string) pbresource.ResourceServiceClient { + return pbresource.NewResourceServiceClient(s.grpcConns[clusterName]) +} + +func (s *Sprawl) getManagementTokenContext(ctx context.Context, clusterName string) context.Context { + mgmtToken := s.secrets.ReadGeneric(clusterName, secrets.BootstrapToken) + //nolint:staticcheck + return context.WithValue(ctx, "x-consul-token", mgmtToken) +} + func getLeader(client *api.Client) (string, error) { leaderAdd, err := client.Status().Leader() if err != nil { diff --git a/testing/deployer/sprawl/details.go b/testing/deployer/sprawl/details.go index c463d310d8..b12fa02e5a 100644 --- a/testing/deployer/sprawl/details.go +++ b/testing/deployer/sprawl/details.go @@ -70,10 +70,15 @@ func (s *Sprawl) PrintDetails() error { Service: svc.ID.String(), }) } else { + ports := make(map[string]int) + for name, port := range svc.Ports { + ports[name] = node.ExposedPort(port) + } cd.Apps = append(cd.Apps, appDetail{ Type: "app", Container: node.DockerName(), ExposedPort: node.ExposedPort(svc.Port), + ExposedPorts: ports, ExposedEnvoyAdminPort: node.ExposedPort(svc.EnvoyAdminPort), Addresses: addrs, Service: svc.ID.String(), @@ -115,19 +120,23 @@ func (s *Sprawl) PrintDetails() error { return false } - if a.Service < b.Service { - return true - } else if a.Service > b.Service { - return false - } - - return a.ExposedPort < b.ExposedPort + return a.Service < b.Service }) for _, d := range cluster.Apps { if d.Type == "server" && d.Container == cluster.Leader { d.Type = "leader" } - portStr := "app=" + strconv.Itoa(d.ExposedPort) + var portStr string + if len(d.ExposedPorts) > 0 { + var out []string + for name, exposed := range d.ExposedPorts { + out = append(out, fmt.Sprintf("app:%s=%d", name, exposed)) + } + sort.Strings(out) + portStr = strings.Join(out, " ") + } else { + portStr = "app=" + strconv.Itoa(d.ExposedPort) + } if d.ExposedEnvoyAdminPort > 0 { portStr += " envoy=" + strconv.Itoa(d.ExposedEnvoyAdminPort) } @@ -166,8 +175,9 @@ type appDetail struct { Type string // server|mesh-gateway|app Container string Addresses []string - ExposedPort int `json:",omitempty"` - ExposedEnvoyAdminPort int `json:",omitempty"` + ExposedPort int `json:",omitempty"` + ExposedPorts map[string]int `json:",omitempty"` + ExposedEnvoyAdminPort int `json:",omitempty"` // just services Service string `json:",omitempty"` } diff --git a/testing/deployer/sprawl/grpc.go b/testing/deployer/sprawl/grpc.go new file mode 100644 index 0000000000..fc4927bb32 --- /dev/null +++ b/testing/deployer/sprawl/grpc.go @@ -0,0 +1,42 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package sprawl + +import ( + "context" + "crypto/tls" + "fmt" + + "github.com/hashicorp/go-rootcerts" + "google.golang.org/grpc" + + "github.com/hashicorp/consul/testing/deployer/sprawl/internal/secrets" + "github.com/hashicorp/consul/testing/deployer/topology" + "github.com/hashicorp/consul/testing/deployer/util" +) + +func (s *Sprawl) dialServerGRPC(cluster *topology.Cluster, node *topology.Node, token string) (*grpc.ClientConn, func(), error) { + var ( + logger = s.logger.With("cluster", cluster.Name) + ) + + tls := &tls.Config{ + ServerName: fmt.Sprintf("server.%s.consul", cluster.Datacenter), + } + + rootConfig := &rootcerts.Config{ + CACertificate: []byte(s.secrets.ReadGeneric(cluster.Name, secrets.CAPEM)), + } + if err := rootcerts.ConfigureTLS(tls, rootConfig); err != nil { + return nil, nil, err + } + + return util.DialExposedGRPCConn( + context.Background(), + logger, + node.ExposedPort(8503), + token, + tls, + ) +} diff --git a/testing/deployer/sprawl/internal/secrets/store.go b/testing/deployer/sprawl/internal/secrets/store.go index 0cacf88b25..85e2741e9f 100644 --- a/testing/deployer/sprawl/internal/secrets/store.go +++ b/testing/deployer/sprawl/internal/secrets/store.go @@ -18,6 +18,7 @@ const ( GossipKey = "gossip" BootstrapToken = "bootstrap-token" AgentRecovery = "agent-recovery" + CAPEM = "ca-pem" ) func (s *Store) SaveGeneric(cluster, name, value string) { diff --git a/testing/deployer/sprawl/internal/tfgen/agent.go b/testing/deployer/sprawl/internal/tfgen/agent.go index 00e7276783..ee77c09a58 100644 --- a/testing/deployer/sprawl/internal/tfgen/agent.go +++ b/testing/deployer/sprawl/internal/tfgen/agent.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/consul/testing/deployer/topology" ) -func (g *Generator) generateAgentHCL(node *topology.Node) string { +func (g *Generator) generateAgentHCL(node *topology.Node, enableV2 bool) string { if !node.IsAgent() { panic("generateAgentHCL only applies to agents") } @@ -35,6 +35,10 @@ func (g *Generator) generateAgentHCL(node *topology.Node) string { b.add("enable_debug", true) b.add("use_streaming_backend", true) + if enableV2 { + b.addSlice("experiments", []string{"resource-apis"}) + } + // speed up leaves b.addBlock("performance", func() { b.add("leave_drain_time", "50ms") diff --git a/testing/deployer/sprawl/internal/tfgen/docker_test.go b/testing/deployer/sprawl/internal/tfgen/docker_test.go index 942b87189f..b657a4d012 100644 --- a/testing/deployer/sprawl/internal/tfgen/docker_test.go +++ b/testing/deployer/sprawl/internal/tfgen/docker_test.go @@ -12,7 +12,6 @@ import ( func TestDockerImageResourceName(t *testing.T) { fn := DockerImageResourceName - assert.Equal(t, "", fn("")) assert.Equal(t, "abcdefghijklmnopqrstuvwxyz0123456789-", fn("abcdefghijklmnopqrstuvwxyz0123456789-")) assert.Equal(t, "hashicorp-consul-1-15-0", fn("hashicorp/consul:1.15.0")) } diff --git a/testing/deployer/sprawl/internal/tfgen/nodes.go b/testing/deployer/sprawl/internal/tfgen/nodes.go index 33a820a3e3..6f105b6f5a 100644 --- a/testing/deployer/sprawl/internal/tfgen/nodes.go +++ b/testing/deployer/sprawl/internal/tfgen/nodes.go @@ -67,7 +67,7 @@ func (g *Generator) generateNodeContainers( }{ terraformPod: pod, ImageResource: DockerImageResourceName(node.Images.Consul), - HCL: g.generateAgentHCL(node), + HCL: g.generateAgentHCL(node, cluster.EnableV2 && node.IsServer()), EnterpriseLicense: g.license, })) } diff --git a/testing/deployer/sprawl/internal/tfgen/templates/container-app-dataplane.tf.tmpl b/testing/deployer/sprawl/internal/tfgen/templates/container-app-dataplane.tf.tmpl index 040cabbf11..fa44090c80 100644 --- a/testing/deployer/sprawl/internal/tfgen/templates/container-app-dataplane.tf.tmpl +++ b/testing/deployer/sprawl/internal/tfgen/templates/container-app-dataplane.tf.tmpl @@ -1,7 +1,7 @@ resource "docker_container" "{{.Node.DockerName}}-{{.Service.ID.TFString}}-sidecar" { name = "{{.Node.DockerName}}-{{.Service.ID.TFString}}-sidecar" network_mode = "container:${docker_container.{{.PodName}}.id}" - image = docker_image.{{.ImageResource}}.latest + image = docker_image.{{.ImageResource}}.image_id restart = "on-failure" {{- range $k, $v := .Labels }} @@ -18,24 +18,34 @@ resource "docker_container" "{{.Node.DockerName}}-{{.Service.ID.TFString}}-sidec } env = [ - "DP_CONSUL_ADDRESSES=server.{{.Node.Cluster}}-consulcluster.lan", - "DP_SERVICE_NODE_NAME={{.Node.PodName}}", - "DP_PROXY_SERVICE_ID={{.Service.ID.Name}}-sidecar-proxy", - {{ if .Enterprise }} - "DP_SERVICE_NAMESPACE={{.Service.ID.Namespace}}", - "DP_SERVICE_PARTITION={{.Service.ID.Partition}}", - {{ end }} - {{ if .Token }} - "DP_CREDENTIAL_TYPE=static", - "DP_CREDENTIAL_STATIC_TOKEN={{.Token}}", - {{ end }} - // for demo purposes - "DP_ENVOY_ADMIN_BIND_ADDRESS=0.0.0.0", - "DP_ENVOY_ADMIN_BIND_PORT=19000", - "DP_LOG_LEVEL=trace", - "DP_CA_CERTS=/consul/config/certs/consul-agent-ca.pem", - "DP_CONSUL_GRPC_PORT=8503", - "DP_TLS_SERVER_NAME=server.{{.Node.Datacenter}}.consul", + "DP_CONSUL_ADDRESSES=server.{{.Node.Cluster}}-consulcluster.lan", +{{ if .Node.IsV2 }} + "DP_PROXY_ID={{.Service.Workload}}", +{{ if .Enterprise }} + "DP_PROXY_NAMESPACE={{.Service.ID.Namespace}}", + "DP_PROXY_PARTITION={{.Service.ID.Partition}}", +{{ end }} +{{ else }} + "DP_SERVICE_NODE_NAME={{.Node.PodName}}", + "DP_PROXY_SERVICE_ID={{.Service.ID.Name}}-sidecar-proxy", +{{ if .Enterprise }} + "DP_SERVICE_NAMESPACE={{.Service.ID.Namespace}}", + "DP_SERVICE_PARTITION={{.Service.ID.Partition}}", +{{ end }} +{{ end }} + +{{ if .Token }} + "DP_CREDENTIAL_TYPE=static", + "DP_CREDENTIAL_STATIC_TOKEN={{.Token}}", +{{ end }} + + // for demo purposes + "DP_ENVOY_ADMIN_BIND_ADDRESS=0.0.0.0", + "DP_ENVOY_ADMIN_BIND_PORT=19000", + "DP_LOG_LEVEL=trace", + "DP_CA_CERTS=/consul/config/certs/consul-agent-ca.pem", + "DP_CONSUL_GRPC_PORT=8503", + "DP_TLS_SERVER_NAME=server.{{.Node.Datacenter}}.consul", ] command = [ diff --git a/testing/deployer/sprawl/internal/tfgen/templates/container-app-sidecar.tf.tmpl b/testing/deployer/sprawl/internal/tfgen/templates/container-app-sidecar.tf.tmpl index 15a5f69226..c368d16104 100644 --- a/testing/deployer/sprawl/internal/tfgen/templates/container-app-sidecar.tf.tmpl +++ b/testing/deployer/sprawl/internal/tfgen/templates/container-app-sidecar.tf.tmpl @@ -1,7 +1,7 @@ resource "docker_container" "{{.Node.DockerName}}-{{.Service.ID.TFString}}-sidecar" { name = "{{.Node.DockerName}}-{{.Service.ID.TFString}}-sidecar" network_mode = "container:${docker_container.{{.PodName}}.id}" - image = docker_image.{{.ImageResource}}.latest + image = docker_image.{{.ImageResource}}.image_id restart = "on-failure" {{- range $k, $v := .Labels }} diff --git a/testing/deployer/sprawl/internal/tfgen/templates/container-app.tf.tmpl b/testing/deployer/sprawl/internal/tfgen/templates/container-app.tf.tmpl index d6033587b1..6cdd7705ea 100644 --- a/testing/deployer/sprawl/internal/tfgen/templates/container-app.tf.tmpl +++ b/testing/deployer/sprawl/internal/tfgen/templates/container-app.tf.tmpl @@ -1,7 +1,7 @@ resource "docker_container" "{{.Node.DockerName}}-{{.Service.ID.TFString}}" { name = "{{.Node.DockerName}}-{{.Service.ID.TFString}}" network_mode = "container:${docker_container.{{.PodName}}.id}" - image = docker_image.{{.ImageResource}}.latest + image = docker_image.{{.ImageResource}}.image_id restart = "on-failure" {{- range $k, $v := .Labels }} diff --git a/testing/deployer/sprawl/internal/tfgen/templates/container-consul.tf.tmpl b/testing/deployer/sprawl/internal/tfgen/templates/container-consul.tf.tmpl index fac148f29c..7b76e0569b 100644 --- a/testing/deployer/sprawl/internal/tfgen/templates/container-consul.tf.tmpl +++ b/testing/deployer/sprawl/internal/tfgen/templates/container-consul.tf.tmpl @@ -1,7 +1,7 @@ resource "docker_container" "{{.Node.DockerName}}" { name = "{{.Node.DockerName}}" network_mode = "container:${docker_container.{{.PodName}}.id}" - image = docker_image.{{.ImageResource}}.latest + image = docker_image.{{.ImageResource}}.image_id restart = "always" env = [ diff --git a/testing/deployer/sprawl/internal/tfgen/templates/container-coredns.tf.tmpl b/testing/deployer/sprawl/internal/tfgen/templates/container-coredns.tf.tmpl index 7789376a98..97c0d7011d 100644 --- a/testing/deployer/sprawl/internal/tfgen/templates/container-coredns.tf.tmpl +++ b/testing/deployer/sprawl/internal/tfgen/templates/container-coredns.tf.tmpl @@ -1,6 +1,6 @@ resource "docker_container" "{{.DockerNetworkName}}-coredns" { name = "{{.DockerNetworkName}}-coredns" - image = docker_image.coredns.latest + image = docker_image.coredns.image_id restart = "always" dns = ["8.8.8.8"] diff --git a/testing/deployer/sprawl/internal/tfgen/templates/container-mgw-dataplane.tf.tmpl b/testing/deployer/sprawl/internal/tfgen/templates/container-mgw-dataplane.tf.tmpl index 18152e68eb..4aac9b5946 100644 --- a/testing/deployer/sprawl/internal/tfgen/templates/container-mgw-dataplane.tf.tmpl +++ b/testing/deployer/sprawl/internal/tfgen/templates/container-mgw-dataplane.tf.tmpl @@ -1,7 +1,7 @@ resource "docker_container" "{{.Node.DockerName}}-{{.Service.ID.TFString}}" { name = "{{.Node.DockerName}}-{{.Service.ID.TFString}}" network_mode = "container:${docker_container.{{.PodName}}.id}" - image = docker_image.{{.ImageResource}}.latest + image = docker_image.{{.ImageResource}}.image_id restart = "on-failure" {{- range $k, $v := .Labels }} diff --git a/testing/deployer/sprawl/internal/tfgen/templates/container-mgw.tf.tmpl b/testing/deployer/sprawl/internal/tfgen/templates/container-mgw.tf.tmpl index 78c4abe8df..68ccbbd0f1 100644 --- a/testing/deployer/sprawl/internal/tfgen/templates/container-mgw.tf.tmpl +++ b/testing/deployer/sprawl/internal/tfgen/templates/container-mgw.tf.tmpl @@ -1,7 +1,7 @@ resource "docker_container" "{{.Node.DockerName}}-{{.Service.ID.TFString}}" { name = "{{.Node.DockerName}}-{{.Service.ID.TFString}}" network_mode = "container:${docker_container.{{.PodName}}.id}" - image = docker_image.{{.ImageResource}}.latest + image = docker_image.{{.ImageResource}}.image_id restart = "on-failure" {{- range $k, $v := .Labels }} diff --git a/testing/deployer/sprawl/internal/tfgen/templates/container-pause.tf.tmpl b/testing/deployer/sprawl/internal/tfgen/templates/container-pause.tf.tmpl index 1f1627b071..ef537f0352 100644 --- a/testing/deployer/sprawl/internal/tfgen/templates/container-pause.tf.tmpl +++ b/testing/deployer/sprawl/internal/tfgen/templates/container-pause.tf.tmpl @@ -1,6 +1,6 @@ resource "docker_container" "{{.PodName}}" { name = "{{.PodName}}" - image = docker_image.pause.latest + image = docker_image.pause.image_id hostname = "{{.PodName}}" restart = "always" dns = ["{{.DNSAddress}}"] diff --git a/testing/deployer/sprawl/internal/tfgen/templates/container-proxy.tf.tmpl b/testing/deployer/sprawl/internal/tfgen/templates/container-proxy.tf.tmpl index ed44d8343f..f4d7fe1d94 100644 --- a/testing/deployer/sprawl/internal/tfgen/templates/container-proxy.tf.tmpl +++ b/testing/deployer/sprawl/internal/tfgen/templates/container-proxy.tf.tmpl @@ -1,6 +1,6 @@ resource "docker_container" "{{.DockerNetworkName}}-forwardproxy" { name = "{{.DockerNetworkName}}-forwardproxy" - image = docker_image.nginx.latest + image = docker_image.nginx.image_id restart = "always" dns = ["8.8.8.8"] diff --git a/testing/deployer/sprawl/resources.go b/testing/deployer/sprawl/resources.go new file mode 100644 index 0000000000..da0ea260dd --- /dev/null +++ b/testing/deployer/sprawl/resources.go @@ -0,0 +1,22 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package sprawl + +import ( + "github.com/hashicorp/consul/testing/deployer/topology" +) + +func (s *Sprawl) populateInitialResources(cluster *topology.Cluster) error { + if len(cluster.InitialResources) == 0 { + return nil + } + + for _, res := range cluster.InitialResources { + if _, err := s.writeResource(cluster, res); err != nil { + return err + } + } + + return nil +} diff --git a/testing/deployer/sprawl/sprawl.go b/testing/deployer/sprawl/sprawl.go index 16743b8b7b..c9dd1eb4e6 100644 --- a/testing/deployer/sprawl/sprawl.go +++ b/testing/deployer/sprawl/sprawl.go @@ -16,9 +16,11 @@ import ( "time" "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-multierror" "github.com/mitchellh/copystructure" + "google.golang.org/grpc" "github.com/hashicorp/consul/testing/deployer/sprawl/internal/runner" "github.com/hashicorp/consul/testing/deployer/sprawl/internal/secrets" @@ -43,7 +45,9 @@ type Sprawl struct { topology *topology.Topology generator *tfgen.Generator - clients map[string]*api.Client // one per cluster + clients map[string]*api.Client // one per cluster + grpcConns map[string]*grpc.ClientConn // one per cluster (when v2 enabled) + grpcConnCancel map[string]func() // one per cluster (when v2 enabled) } // Topology allows access to the topology that defines the resources. Do not @@ -60,6 +64,12 @@ func (s *Sprawl) Config() *topology.Config { return c2 } +// ResourceServiceClientForCluster returns a shared common client that defaults +// to using the management token for this cluster. +func (s *Sprawl) ResourceServiceClientForCluster(clusterName string) pbresource.ResourceServiceClient { + return pbresource.NewResourceServiceClient(s.grpcConns[clusterName]) +} + func (s *Sprawl) HTTPClientForCluster(clusterName string) (*http.Client, error) { cluster, ok := s.topology.Clusters[clusterName] if !ok { @@ -167,10 +177,12 @@ func Launch( } s := &Sprawl{ - logger: logger, - runner: runner, - workdir: workdir, - clients: make(map[string]*api.Client), + logger: logger, + runner: runner, + workdir: workdir, + clients: make(map[string]*api.Client), + grpcConns: make(map[string]*grpc.ClientConn), + grpcConnCancel: make(map[string]func()), } if err := s.ensureLicense(); err != nil { diff --git a/testing/deployer/sprawl/sprawltest/sprawltest.go b/testing/deployer/sprawl/sprawltest/sprawltest.go index 7b1e0493d7..d695612860 100644 --- a/testing/deployer/sprawl/sprawltest/sprawltest.go +++ b/testing/deployer/sprawl/sprawltest/sprawltest.go @@ -13,10 +13,13 @@ import ( "sync" "testing" + "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-multierror" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" "github.com/hashicorp/consul/testing/deployer/sprawl" "github.com/hashicorp/consul/testing/deployer/sprawl/internal/runner" @@ -206,3 +209,10 @@ func SkipIfTerraformNotPresent(t *testing.T) { t.Skipf("%q not found on $PATH - download and install to run this test", terraformBinaryName) } } + +func MustSetResourceData(t *testing.T, res *pbresource.Resource, data proto.Message) *pbresource.Resource { + anyData, err := anypb.New(data) + require.NoError(t, err) + res.Data = anyData + return res +} diff --git a/testing/deployer/sprawl/sprawltest/test_test.go b/testing/deployer/sprawl/sprawltest/test_test.go index 1bb69ea77e..8a098c92ab 100644 --- a/testing/deployer/sprawl/sprawltest/test_test.go +++ b/testing/deployer/sprawl/sprawltest/test_test.go @@ -8,17 +8,160 @@ import ( "testing" "github.com/hashicorp/consul/api" + pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" "github.com/stretchr/testify/require" "github.com/hashicorp/consul/testing/deployer/sprawl/sprawltest" "github.com/hashicorp/consul/testing/deployer/topology" ) +func TestSprawl_CatalogV2(t *testing.T) { + serversDC1 := newTopologyServerSet("dc1-server", 3, []string{"dc1", "wan"}, nil) + + cfg := &topology.Config{ + Images: topology.Images{ + ConsulCE: "hashicorppreview/consul:1.17-dev", + ConsulEnterprise: "hashicorppreview/consul-enterprise:1.17-dev", + Dataplane: "hashicorppreview/consul-dataplane:1.3-dev", + }, + Networks: []*topology.Network{ + {Name: "dc1"}, + {Name: "wan", Type: "wan"}, + }, + Clusters: []*topology.Cluster{ + { + Enterprise: true, + Name: "dc1", + Nodes: topology.MergeSlices(serversDC1, []*topology.Node{ + { + Kind: topology.NodeKindDataplane, + Version: topology.NodeVersionV2, + Name: "dc1-client1", + Services: []*topology.Service{ + { + ID: topology.ServiceID{Name: "ping"}, + Image: "rboyer/pingpong:latest", + Port: 8080, + EnvoyAdminPort: 19000, + Command: []string{ + "-bind", "0.0.0.0:8080", + "-dial", "127.0.0.1:9090", + "-pong-chaos", + "-dialfreq", "250ms", + "-name", "ping", + }, + Upstreams: []*topology.Upstream{{ + ID: topology.ServiceID{Name: "pong"}, + LocalPort: 9090, + }}, + }, + }, + }, + { + Kind: topology.NodeKindDataplane, + Version: topology.NodeVersionV2, + Name: "dc1-client2", + Services: []*topology.Service{ + { + ID: topology.ServiceID{Name: "pong"}, + Image: "rboyer/pingpong:latest", + Port: 8080, + EnvoyAdminPort: 19000, + Command: []string{ + "-bind", "0.0.0.0:8080", + "-dial", "127.0.0.1:9090", + "-pong-chaos", + "-dialfreq", "250ms", + "-name", "pong", + }, + Upstreams: []*topology.Upstream{{ + ID: topology.ServiceID{Name: "ping"}, + LocalPort: 9090, + }}, + }, + }, + }, + }), + InitialResources: []*pbresource.Resource{ + sprawltest.MustSetResourceData(t, &pbresource.Resource{ + Id: &pbresource.ID{ + Type: pbmesh.HTTPRouteType, + Name: "test-http-route", + }, + }, &pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{{ + Ref: &pbresource.Reference{ + Type: pbcatalog.ServiceType, + Name: "test", + }, + }}, + }), + sprawltest.MustSetResourceData(t, &pbresource.Resource{ + Id: &pbresource.ID{ + Type: pbauth.TrafficPermissionsType, + Name: "ping-perms", + }, + }, &pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "ping", + }, + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{{ + Sources: []*pbauth.Source{{ + IdentityName: "pong", + }}, + }}, + }), + sprawltest.MustSetResourceData(t, &pbresource.Resource{ + Id: &pbresource.ID{ + Type: pbauth.TrafficPermissionsType, + Name: "pong-perms", + }, + }, &pbauth.TrafficPermissions{ + Destination: &pbauth.Destination{ + IdentityName: "pong", + }, + Action: pbauth.Action_ACTION_ALLOW, + Permissions: []*pbauth.Permission{{ + Sources: []*pbauth.Source{{ + IdentityName: "ping", + }}, + }}, + }), + }, + }, + }, + } + + sp := sprawltest.Launch(t, cfg) + + for _, cluster := range sp.Topology().Clusters { + leader, err := sp.Leader(cluster.Name) + require.NoError(t, err) + t.Logf("%s: leader = %s", cluster.Name, leader.ID()) + + followers, err := sp.Followers(cluster.Name) + require.NoError(t, err) + for _, f := range followers { + t.Logf("%s: follower = %s", cluster.Name, f.ID()) + } + } +} + func TestSprawl(t *testing.T) { serversDC1 := newTopologyServerSet("dc1-server", 3, []string{"dc1", "wan"}, nil) serversDC2 := newTopologyServerSet("dc2-server", 3, []string{"dc2", "wan"}, nil) cfg := &topology.Config{ + Images: topology.Images{ + // ConsulEnterprise: "consul-dev:latest", + ConsulCE: "hashicorppreview/consul:1.17-dev", + ConsulEnterprise: "hashicorppreview/consul-enterprise:1.17-dev", + Dataplane: "hashicorppreview/consul-dataplane:1.3-dev", + }, Networks: []*topology.Network{ {Name: "dc1"}, {Name: "dc2"}, @@ -116,6 +259,31 @@ func TestSprawl(t *testing.T) { }, }, }, + { + Kind: topology.NodeKindDataplane, + Version: topology.NodeVersionV2, + Name: "dc2-client3", + Services: []*topology.Service{ + { + ID: topology.ServiceID{Name: "pong"}, + Image: "rboyer/pingpong:latest", + Port: 8080, + EnvoyAdminPort: 19000, + Command: []string{ + "-bind", "0.0.0.0:8080", + "-dial", "127.0.0.1:9090", + "-pong-chaos", + "-dialfreq", "250ms", + "-name", "pong", + }, + Upstreams: []*topology.Upstream{{ + ID: topology.ServiceID{Name: "ping"}, + LocalPort: 9090, + Peer: "peer-dc1-default", + }}, + }, + }, + }, }), InitialConfigEntries: []api.ConfigEntry{ &api.ExportedServicesConfigEntry{ diff --git a/testing/deployer/sprawl/tls.go b/testing/deployer/sprawl/tls.go index bc5489206c..d1e3fd6836 100644 --- a/testing/deployer/sprawl/tls.go +++ b/testing/deployer/sprawl/tls.go @@ -9,6 +9,7 @@ import ( "fmt" "io" + "github.com/hashicorp/consul/testing/deployer/sprawl/internal/secrets" "github.com/hashicorp/consul/testing/deployer/topology" ) @@ -113,6 +114,29 @@ fi if err != nil { return fmt.Errorf("could not create all necessary TLS certificates in docker volume: %v", err) } + + var capture bytes.Buffer + err = s.runner.DockerExec(ctx, []string{"run", + "--rm", + "-i", + "--net=none", + "-u", consulUserArg, + "-v", cluster.TLSVolumeName + ":/data", + "-w", "/data", + "busybox:1.34", + "cat", + "/data/consul-agent-ca.pem", + }, &capture, nil) + if err != nil { + return fmt.Errorf("could not read CA PEM from docker volume: %v", err) + } + + caPEM := capture.String() + if caPEM == "" { + return fmt.Errorf("found empty CA PEM") + } + + s.secrets.SaveGeneric(cluster.Name, secrets.CAPEM, caPEM) } return nil diff --git a/testing/deployer/topology/compile.go b/testing/deployer/topology/compile.go index 7faf74d01a..c9249d43ed 100644 --- a/testing/deployer/topology/compile.go +++ b/testing/deployer/topology/compile.go @@ -13,10 +13,17 @@ import ( "sort" "github.com/google/go-cmp/cmp" + pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" + pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/go-hclog" + "golang.org/x/exp/maps" + + "github.com/hashicorp/consul/testing/deployer/util" ) -const DockerPrefix = "consulcluster" +const DockerPrefix = "cslc" // ConSuLCluster func Compile(logger hclog.Logger, raw *Config) (*Topology, error) { return compile(logger, raw, nil) @@ -122,6 +129,22 @@ func compile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error return nil, fmt.Errorf("cluster %q has no nodes", c.Name) } + if len(c.Services) == 0 { // always initialize this regardless of v2-ness, because we might late-enable it below + c.Services = make(map[ServiceID]*pbcatalog.Service) + } + + var implicitV2Services bool + if len(c.Services) > 0 { + c.EnableV2 = true + for name, svc := range c.Services { + if svc.Workloads != nil { + return nil, fmt.Errorf("the workloads field for v2 service %q is not user settable", name) + } + } + } else { + implicitV2Services = true + } + if c.TLSVolumeName != "" { return nil, fmt.Errorf("user cannot specify the TLSVolumeName field") } @@ -149,6 +172,39 @@ func compile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error addTenancy(ce.GetPartition(), ce.GetNamespace()) } + if len(c.InitialResources) > 0 { + c.EnableV2 = true + } + for _, res := range c.InitialResources { + if res.Id.Tenancy == nil { + res.Id.Tenancy = &pbresource.Tenancy{} + } + switch res.Id.Tenancy.PeerName { + case "", "local": + default: + return nil, fmt.Errorf("resources cannot target non-local peers") + } + res.Id.Tenancy.Partition = PartitionOrDefault(res.Id.Tenancy.Partition) + res.Id.Tenancy.Namespace = NamespaceOrDefault(res.Id.Tenancy.Namespace) + + switch { + case util.EqualType(pbauth.ComputedTrafficPermissionsType, res.Id.GetType()), + util.EqualType(pbauth.WorkloadIdentityType, res.Id.GetType()): + fallthrough + case util.EqualType(pbmesh.ComputedRoutesType, res.Id.GetType()), + util.EqualType(pbmesh.ProxyStateTemplateType, res.Id.GetType()): + fallthrough + case util.EqualType(pbcatalog.HealthChecksType, res.Id.GetType()), + util.EqualType(pbcatalog.HealthStatusType, res.Id.GetType()), + util.EqualType(pbcatalog.NodeType, res.Id.GetType()), + util.EqualType(pbcatalog.ServiceEndpointsType, res.Id.GetType()), + util.EqualType(pbcatalog.WorkloadType, res.Id.GetType()): + return nil, fmt.Errorf("you should not create a resource of type %q this way", util.TypeToString(res.Id.Type)) + } + + addTenancy(res.Id.Tenancy.Partition, res.Id.Tenancy.Namespace) + } + seenNodes := make(map[NodeID]struct{}) for _, n := range c.Nodes { if n.Name == "" { @@ -164,6 +220,20 @@ func compile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error return nil, fmt.Errorf("cluster %q node %q has invalid kind: %s", c.Name, n.Name, n.Kind) } + if n.Version == NodeVersionUnknown { + n.Version = NodeVersionV1 + } + switch n.Version { + case NodeVersionV1: + case NodeVersionV2: + if n.Kind == NodeKindClient { + return nil, fmt.Errorf("v2 does not support client agents at this time") + } + c.EnableV2 = true + default: + return nil, fmt.Errorf("cluster %q node %q has invalid version: %s", c.Name, n.Name, n.Version) + } + n.Partition = PartitionOrDefault(n.Partition) if !IsValidLabel(n.Partition) { return nil, fmt.Errorf("node partition is not valid: %s", n.Partition) @@ -257,6 +327,10 @@ func compile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error // Denormalize svc.Node = n + svc.NodeVersion = n.Version + if n.IsV2() { + svc.Workload = svc.ID.Name + "-" + n.PodName() + } if !IsValidLabel(svc.ID.Partition) { return nil, fmt.Errorf("service partition is not valid: %s", svc.ID.Partition) @@ -330,15 +404,79 @@ func compile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error foundPeerNames[c.Name][u.Peer] = struct{}{} } - if u.ID.Name == "" { - return nil, fmt.Errorf("upstream service name is required") - } addTenancy(u.ID.Partition, u.ID.Namespace) + + if u.LocalAddress == "" { + // v1 defaults to 127.0.0.1 but v2 does not. Safe to do this generally though. + u.LocalAddress = "127.0.0.1" + } + + if u.PortName != "" && n.IsV1() { + return nil, fmt.Errorf("explicit upstreams cannot use port names in v1") + } + if u.PortName == "" && n.IsV2() { + // Assume this is a v1->v2 conversion and name it. + u.PortName = "legacy" + } } if err := svc.Validate(); err != nil { return nil, fmt.Errorf("cluster %q node %q service %q is not valid: %w", c.Name, n.Name, svc.ID.String(), err) } + + if n.IsV2() { + if implicitV2Services { + svc.V2Services = []string{svc.ID.Name} + + var svcPorts []*pbcatalog.ServicePort + for name := range svc.Ports { + svcPorts = append(svcPorts, &pbcatalog.ServicePort{ + TargetPort: name, + Protocol: pbcatalog.Protocol_PROTOCOL_TCP, // TODO + }) + } + if !svc.DisableServiceMesh { + svcPorts = append(svcPorts, &pbcatalog.ServicePort{ + TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH, + }) + } + + v2svc := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Names: []string{svc.Workload}, + }, + Ports: svcPorts, + } + + c.Services[svc.ID] = v2svc + + } else { + for _, name := range svc.V2Services { + v2ID := NewServiceID(name, svc.ID.Namespace, svc.ID.Partition) + + v2svc, ok := c.Services[v2ID] + if !ok { + return nil, fmt.Errorf("cluster %q node %q service %q has a v2 service reference that does not exist %q", + c.Name, n.Name, svc.ID.String(), name) + } + if v2svc.Workloads == nil { + v2svc.Workloads = &pbcatalog.WorkloadSelector{} + } + v2svc.Workloads.Names = append(v2svc.Workloads.Names, svc.Workload) + } + } + + if len(svc.WorkloadIdentities) == 0 { + svc.WorkloadIdentities = []string{svc.ID.Name} + } + } else { + if len(svc.V2Services) > 0 { + return nil, fmt.Errorf("cannot specify v2 services for v1") + } + if len(svc.WorkloadIdentities) > 0 { + return nil, fmt.Errorf("cannot specify workload identities for v1") + } + } } } @@ -519,6 +657,9 @@ func compile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error if len(newCluster.InitialConfigEntries) > 0 { logger.Warn("initial config entries were provided, but are skipped on recompile") } + if len(newCluster.InitialResources) > 0 { + logger.Warn("initial resources were provided, but are skipped on recompile") + } // Check NODES if err := inheritAndValidateNodes(oldCluster.Nodes, newCluster.Nodes); err != nil { @@ -553,6 +694,7 @@ func inheritAndValidateNodes( } if currNode.Node.Kind != node.Kind || + currNode.Node.Version != node.Version || currNode.Node.Partition != node.Partition || currNode.Node.Name != node.Name || currNode.Node.Index != node.Index || @@ -589,6 +731,7 @@ func inheritAndValidateNodes( if currSvc.ID != svc.ID || currSvc.Port != svc.Port || + !maps.Equal(currSvc.Ports, svc.Ports) || currSvc.EnvoyAdminPort != svc.EnvoyAdminPort || currSvc.EnvoyPublicListenerPort != svc.EnvoyPublicListenerPort || isSame(currSvc.Command, svc.Command) != nil || diff --git a/testing/deployer/topology/default_cdp.go b/testing/deployer/topology/default_cdp.go deleted file mode 100644 index f20df3d1a9..0000000000 --- a/testing/deployer/topology/default_cdp.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package topology - -const DefaultDataplaneImage = "hashicorp/consul-dataplane:1.2.1" diff --git a/testing/deployer/topology/default_consul.go b/testing/deployer/topology/default_consul.go deleted file mode 100644 index 3a259d12d4..0000000000 --- a/testing/deployer/topology/default_consul.go +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package topology - -const DefaultConsulImage = "hashicorp/consul:1.16.2" -const DefaultConsulEnterpriseImage = "hashicorp/consul-enterprise:1.16.2-ent" diff --git a/testing/deployer/topology/default_versions.go b/testing/deployer/topology/default_versions.go new file mode 100644 index 0000000000..b35bb7ecfa --- /dev/null +++ b/testing/deployer/topology/default_versions.go @@ -0,0 +1,13 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +// Code generated by update-latest-versions.sh. DO NOT EDIT. + +package topology + +const ( + DefaultConsulImage = "hashicorp/consul:1.16.2" + DefaultConsulEnterpriseImage = "hashicorp/consul-enterprise:1.16.2-ent" + DefaultEnvoyImage = "envoyproxy/envoy:v1.26.4" + DefaultDataplaneImage = "hashicorp/consul-dataplane:1.2.2" +) diff --git a/testing/deployer/topology/default_envoy.go b/testing/deployer/topology/generate.go similarity index 62% rename from testing/deployer/topology/default_envoy.go rename to testing/deployer/topology/generate.go index c557a318a1..f9d6e26c3a 100644 --- a/testing/deployer/topology/default_envoy.go +++ b/testing/deployer/topology/generate.go @@ -3,4 +3,4 @@ package topology -const DefaultEnvoyImage = "envoyproxy/envoy:v1.25.1" +//go:generate ../update-latest-versions.sh diff --git a/testing/deployer/topology/ids.go b/testing/deployer/topology/ids.go index 3f964b1254..73106ff4c9 100644 --- a/testing/deployer/topology/ids.go +++ b/testing/deployer/topology/ids.go @@ -69,6 +69,7 @@ func (id NodeID) String() string { func (id NodeID) ACLString() string { return fmt.Sprintf("%s--%s", id.Partition, id.Name) } + func (id NodeID) TFString() string { return id.ACLString() } @@ -111,16 +112,26 @@ func (id ServiceID) String() string { func (id ServiceID) ACLString() string { return fmt.Sprintf("%s--%s--%s", id.Partition, id.Namespace, id.Name) } + func (id ServiceID) TFString() string { return id.ACLString() } +func (id ServiceID) PartitionOrDefault() string { + return PartitionOrDefault(id.Partition) +} + +func (id ServiceID) NamespaceOrDefault() string { + return NamespaceOrDefault(id.Namespace) +} + func PartitionOrDefault(name string) string { if name == "" { return "default" } return name } + func NamespaceOrDefault(name string) string { if name == "" { return "default" diff --git a/testing/deployer/topology/relationships.go b/testing/deployer/topology/relationships.go new file mode 100644 index 0000000000..57c075b77f --- /dev/null +++ b/testing/deployer/topology/relationships.go @@ -0,0 +1,77 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package topology + +import ( + "bytes" + "fmt" + "text/tabwriter" +) + +// ComputeRelationships will analyze a full topology and generate all of the +// downstream/upstream information for all of them. +func (t *Topology) ComputeRelationships() []Relationship { + var out []Relationship + for _, cluster := range t.Clusters { + for _, n := range cluster.Nodes { + for _, s := range n.Services { + for _, u := range s.Upstreams { + out = append(out, Relationship{ + Caller: s, + Upstream: u, + }) + } + } + } + } + return out +} + +// RenderRelationships will take the output of ComputeRelationships and display +// it in tabular form. +func RenderRelationships(ships []Relationship) string { + var buf bytes.Buffer + w := tabwriter.NewWriter(&buf, 0, 0, 3, ' ', tabwriter.Debug) + fmt.Fprintf(w, "DOWN\tnode\tservice\tport\tUP\tservice\t\n") + for _, r := range ships { + fmt.Fprintf(w, + "%s\t%s\t%s\t%d\t%s\t%s\t\n", + r.downCluster(), + r.Caller.Node.ID().String(), + r.Caller.ID.String(), + r.Upstream.LocalPort, + r.upCluster(), + r.Upstream.ID.String(), + ) + } + fmt.Fprintf(w, "\t\t\t\t\t\t\n") + + w.Flush() + return buf.String() +} + +type Relationship struct { + Caller *Service + Upstream *Upstream +} + +func (r Relationship) String() string { + return fmt.Sprintf( + "%s on %s in %s via :%d => %s in %s", + r.Caller.ID.String(), + r.Caller.Node.ID().String(), + r.downCluster(), + r.Upstream.LocalPort, + r.Upstream.ID.String(), + r.upCluster(), + ) +} + +func (r Relationship) downCluster() string { + return r.Caller.Node.Cluster +} + +func (r Relationship) upCluster() string { + return r.Upstream.Cluster +} diff --git a/testing/deployer/topology/topology.go b/testing/deployer/topology/topology.go index 2662329aee..48edf56296 100644 --- a/testing/deployer/topology/topology.go +++ b/testing/deployer/topology/topology.go @@ -12,6 +12,8 @@ import ( "sort" "github.com/hashicorp/consul/api" + pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" + "github.com/hashicorp/consul/proto-public/pbresource" ) type Topology struct { @@ -108,6 +110,45 @@ func (c *Config) Cluster(name string) *Cluster { return nil } +// DisableNode is a no-op if the node is already disabled. +func (c *Config) DisableNode(clusterName string, nid NodeID) (bool, error) { + cluster := c.Cluster(clusterName) + if cluster == nil { + return false, fmt.Errorf("no such cluster: %q", clusterName) + } + + for _, n := range cluster.Nodes { + if n.ID() == nid { + if n.Disabled { + return false, nil + } + n.Disabled = true + return true, nil + } + } + + return false, fmt.Errorf("expected to find nodeID %q in cluster %q", nid.String(), clusterName) +} + +// EnableNode is a no-op if the node is already enabled. +func (c *Config) EnableNode(clusterName string, nid NodeID) (bool, error) { + cluster := c.Cluster(clusterName) + if cluster == nil { + return false, fmt.Errorf("no such cluster: %q", clusterName) + } + + for _, n := range cluster.Nodes { + if n.ID() == nid { + if !n.Disabled { + return false, nil + } + n.Disabled = false + return true, nil + } + } + return false, fmt.Errorf("expected to find nodeID %q in cluster %q", nid.String(), clusterName) +} + type Network struct { Type string // lan/wan ; empty means lan Name string // logical name @@ -198,6 +239,14 @@ type Cluster struct { // components. Enterprise bool `json:",omitempty"` + // Services is a forward declaration of V2 services. This goes in hand with + // the V2Services field on the Service (instance) struct. + // + // Use of this is optional. If you elect not to use it, then v2 Services + // definitions are inferred from the list of service instances defined on + // the nodes in this cluster. + Services map[ServiceID]*pbcatalog.Service `json:"omitempty"` + // Nodes is the definition of the nodes (agent-less and agent-ful). Nodes []*Node @@ -212,11 +261,16 @@ type Cluster struct { // create multiple peer clusters with identical datacenter names. Datacenter string - // InitialConfigEntries is a convenience function to have some config + // InitialConfigEntries is a convenience mechanism to have some config // entries created after the servers start up but before the rest of the // topology comes up. InitialConfigEntries []api.ConfigEntry `json:",omitempty"` + // InitialResources is a convenience mechanism to have some resources + // created after the servers start up but before the rest of the topology + // comes up. + InitialResources []*pbresource.Resource `json:",omitempty"` + // TLSVolumeName is the docker volume name containing the various certs // generated by 'consul tls cert create' // @@ -227,6 +281,10 @@ type Cluster struct { // // Denormalized during compile. Peerings map[string]*PeerCluster `json:",omitempty"` + + // EnableV2 activates V2 on the servers. If any node in the cluster needs + // V2 this will be turned on automatically. + EnableV2 bool `json:",omitempty"` } func (c *Cluster) inheritFromExisting(existing *Cluster) { @@ -422,9 +480,18 @@ const ( NodeKindDataplane NodeKind = "dataplane" ) +type NodeVersion string + +const ( + NodeVersionUnknown NodeVersion = "" + NodeVersionV1 NodeVersion = "v1" + NodeVersionV2 NodeVersion = "v2" +) + // TODO: rename pod type Node struct { Kind NodeKind + Version NodeVersion Partition string // will be not empty Name string // logical name @@ -462,6 +529,9 @@ func (n *Node) DockerName() string { } func (n *Node) ExposedPort(internalPort int) int { + if internalPort == 0 { + return 0 + } return n.usedPorts[internalPort] } @@ -568,6 +638,14 @@ func (n *Node) PublicProxyPort() int { panic("node has no public network") } +func (n *Node) IsV2() bool { + return n.Version == NodeVersionV2 +} + +func (n *Node) IsV1() bool { + return !n.IsV2() +} + func (n *Node) IsServer() bool { return n.Kind == NodeKindServer } @@ -639,12 +717,46 @@ type ServiceAndNode struct { Node *Node } +// TODO(rb): really this should now be called "workload" or "instance" type Service struct { - ID ServiceID - Image string - Port int + ID ServiceID + Image string + + // Port is the v1 single-port of this service. + Port int `json:",omitempty"` + + // Ports is the v2 multi-port list for this service. + // + // This only applies for multi-port (v2). + Ports map[string]int `json:",omitempty"` + + // ExposedPort is the exposed docker port corresponding to 'Port'. ExposedPort int `json:",omitempty"` + // ExposedPorts are the exposed docker ports corresponding to 'Ports'. + // + // This only applies for multi-port (v2). + ExposedPorts map[string]int `json:",omitempty"` + + // V2Services contains service names (which are merged with the tenancy + // info from ID) to resolve services in the Services slice in the Cluster + // definition. + // + // If omitted it is inferred that the ID.Name field is the singular service + // for this workload. + // + // This only applies for multi-port (v2). + V2Services []string `json:",omitempty"` + + // WorkloadIdentities contains named WorkloadIdentities to assign to this + // workload. + // + // If omitted it is inferred that the ID.Name field is the singular + // identity for this workload. + // + // This only applies for multi-port (v2). + WorkloadIdentities []string `json:",omitempty"` + Disabled bool `json:",omitempty"` // TODO // TODO: expose extra port here? @@ -667,17 +779,44 @@ type Service struct { Upstreams []*Upstream // denormalized at topology compile - Node *Node `json:"-"` + Node *Node `json:"-"` + NodeVersion NodeVersion `json:"-"` + Workload string `json:"-"` +} + +func (s *Service) PortOrDefault(name string) int { + if len(s.Ports) > 0 { + return s.Ports[name] + } + return s.Port +} + +func (s *Service) IsV2() bool { + return s.NodeVersion == NodeVersionV2 +} + +func (s *Service) IsV1() bool { + return !s.IsV2() } func (s *Service) inheritFromExisting(existing *Service) { s.ExposedPort = existing.ExposedPort + s.ExposedPorts = existing.ExposedPorts s.ExposedEnvoyAdminPort = existing.ExposedEnvoyAdminPort } func (s *Service) ports() []int { var out []int - if s.Port > 0 { + if len(s.Ports) > 0 { + seen := make(map[int]struct{}) + for _, port := range s.Ports { + if _, ok := seen[port]; !ok { + // It's totally fine to expose the same port twice in a workload. + seen[port] = struct{}{} + out = append(out, port) + } + } + } else if s.Port > 0 { out = append(out, s.Port) } if s.EnvoyAdminPort > 0 { @@ -714,8 +853,27 @@ func (s *Service) Validate() error { if s.Image == "" && !s.IsMeshGateway { return fmt.Errorf("service image is required") } - if s.Port <= 0 { - return fmt.Errorf("service has invalid port") + if s.IsV2() { + if len(s.Ports) > 0 && s.Port > 0 { + return fmt.Errorf("cannot specify both singleport and multiport on service in v2") + } + if s.Port > 0 { + s.Ports = map[string]int{"legacy": s.Port} + s.Port = 0 + } + + for name, port := range s.Ports { + if port <= 0 { + return fmt.Errorf("service has invalid port %q", name) + } + } + } else { + if len(s.Ports) > 0 { + return fmt.Errorf("cannot specify mulitport on service in v1") + } + if s.Port <= 0 { + return fmt.Errorf("service has invalid port") + } } if s.DisableServiceMesh && s.IsMeshGateway { return fmt.Errorf("cannot disable service mesh and still run a mesh gateway") @@ -758,6 +916,11 @@ type Upstream struct { LocalAddress string `json:",omitempty"` // defaults to 127.0.0.1 LocalPort int Peer string `json:",omitempty"` + + // PortName is the named port of this Upstream to route traffic to. + // + // This only applies for multi-port (v2). + PortName string `json:",omitempty"` // TODO: what about mesh gateway mode overrides? // computed at topology compile diff --git a/testing/deployer/update-latest-versions.sh b/testing/deployer/update-latest-versions.sh new file mode 100755 index 0000000000..16f0231422 --- /dev/null +++ b/testing/deployer/update-latest-versions.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +set -euo pipefail + +unset CDPATH + +cd "$(dirname "$0")" + +### +# This script will update the default image names to the latest released versions of +# Consul, Consul Enterprise, and Consul Dataplane. +# +# For Envoy, it will interrogate the latest version of Consul for it's maximum supported +# Envoy version and use that. +### + +readonly consul_latest="hashicorp/consul:latest" +readonly dataplane_latest="hashicorp/consul-dataplane:latest" + +# First pull current versions of some images. +docker pull "$consul_latest" || true +docker pull "$dataplane_latest" || true + +# Read the version from the easy ones directly out of their image labels. +consul_version="$(docker image inspect "$consul_latest" | jq -r '.[0].Config.Labels."org.opencontainers.image.version"')" +dataplane_version="$(docker image inspect "$dataplane_latest" | jq -r '.[0].Config.Labels.version')" + +# Check to see what version of Envoy consul wants. +docker rm -f consul-envoy-check &>/dev/null || true +docker run -d --name consul-envoy-check "$consul_latest" + +envoy_version="" +while true; do + # We have to retry in case consul doesn't fully start up before we get here. + envoy_version="$(docker exec consul-envoy-check sh -c 'wget -q localhost:8500/v1/agent/self -O -' | jq -r '.xDS.SupportedProxies.envoy[0]')" + if [[ -n "$envoy_version" ]]; then + break + fi +done +docker rm -f consul-envoy-check &>/dev/null || true + +cat > topology/default_versions.go <