diff --git a/test-integ/go.mod b/test-integ/go.mod index 8a95aebe91..bef0c14c93 100644 --- a/test-integ/go.mod +++ b/test-integ/go.mod @@ -3,6 +3,7 @@ module github.com/hashicorp/consul/test-integ go 1.20 require ( + github.com/google/go-cmp v0.5.9 github.com/hashicorp/consul/api v1.26.1 github.com/hashicorp/consul/proto-public v0.5.1 github.com/hashicorp/consul/sdk v0.15.0 @@ -48,7 +49,6 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.0.1 // indirect - github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/consul v1.16.1 // indirect diff --git a/test-integ/topoutil/asserter.go b/test-integ/topoutil/asserter.go index 5e986a20d5..894cfaf9b2 100644 --- a/test-integ/topoutil/asserter.go +++ b/test-integ/topoutil/asserter.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -366,17 +367,31 @@ func (a *Asserter) CatalogServiceExists(t *testing.T, cluster string, svc string libassert.CatalogServiceExists(t, cl, svc, opts) } -// AssertServiceHealth asserts whether the given service is healthy or not -func (a *Asserter) AssertServiceHealth(t *testing.T, cl *api.Client, serverSVC string, onlypassing bool, count int) { +// HealthServiceEntries asserts the service has the expected number of instances +func (a *Asserter) HealthServiceEntries(t *testing.T, cluster string, svc string, passingOnly bool, opts *api.QueryOptions, expectedInstance int) []*api.ServiceEntry { t.Helper() - retry.RunWith(&retry.Timer{Timeout: time.Second * 20, Wait: time.Millisecond * 500}, t, func(r *retry.R) { - svcs, _, err := cl.Health().Service( - serverSVC, - "", - onlypassing, - nil, - ) + cl := a.mustGetAPIClient(t, cluster) + health := cl.Health() + + var serviceEntries []*api.ServiceEntry + var err error + retry.RunWith(&retry.Timer{Timeout: 60 * time.Second, Wait: time.Millisecond * 500}, t, func(r *retry.R) { + serviceEntries, _, err = health.Service(svc, "", passingOnly, opts) require.NoError(r, err) - require.Equal(r, count, len(svcs)) + require.Equal(r, expectedInstance, len(serviceEntries)) + }) + + return serviceEntries +} + +// TokenExist asserts the token exists in the cluster and identical to the expected token +func (a *Asserter) TokenExist(t *testing.T, cluster string, expectedToken *api.ACLToken) { + t.Helper() + cl := a.mustGetAPIClient(t, cluster) + acl := cl.ACL() + retry.RunWith(&retry.Timer{Timeout: 60 * time.Second, Wait: time.Millisecond * 500}, t, func(r *retry.R) { + retrievedToken, _, err := acl.TokenRead(expectedToken.AccessorID, &api.QueryOptions{}) + require.NoError(r, err) + require.True(r, cmp.Equal(expectedToken, retrievedToken), "token %s", expectedToken.Description) }) } diff --git a/test-integ/topoutil/http_consul.go b/test-integ/topoutil/http_consul.go new file mode 100644 index 0000000000..27abc0b090 --- /dev/null +++ b/test-integ/topoutil/http_consul.go @@ -0,0 +1,50 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package topoutil + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" +) + +// RequestRegisterService registers a service at the given node address +// using consul http request. +// +// The service definition must be a JSON string. +func RequestRegisterService(clusterHttpCli *http.Client, nodeAddress string, serviceDefinition string, token string) error { + var js json.RawMessage + if err := json.Unmarshal([]byte(serviceDefinition), &js); err != nil { + return fmt.Errorf("failed to unmarshal service definition: %s", err) + } + + u, err := url.Parse(nodeAddress) + if err != nil { + return fmt.Errorf("failed to parse node address: %s", err) + } + u.Path = "/v1/agent/service/register" + + req, err := http.NewRequest(http.MethodPut, u.String(), bytes.NewBuffer(js)) + if err != nil { + return fmt.Errorf("failed to create request: %s", err) + } + + if token != "" { + req.Header.Set("X-Consul-Token", token) + } + + resp, err := clusterHttpCli.Do(req) + if err != nil { + return fmt.Errorf("failed to send request: %s", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("failed to register service: %s", resp.Status) + } + + return nil +} diff --git a/test-integ/upgrade/l7_traffic_management/common.go b/test-integ/upgrade/l7_traffic_management/common.go index 046890335b..db8459b939 100644 --- a/test-integ/upgrade/l7_traffic_management/common.go +++ b/test-integ/upgrade/l7_traffic_management/common.go @@ -5,7 +5,6 @@ package l7_traffic_management import ( "fmt" - "github.com/stretchr/testify/require" "testing" "github.com/hashicorp/consul/api" @@ -191,21 +190,18 @@ func (ct *commonTopo) ValidateWorkloads(t *testing.T) { ct.Assert = topoutil.NewAsserter(ct.Sprawl) cluster := ct.Sprawl.Topology().Clusters[dc1] - cl, err := ct.Sprawl.APIClientForCluster(cluster.Name, "") - require.NoError(t, err) - staticServerWorkload := cluster.WorkloadByID( topology.NewNodeID("dc1-client1", defaultPartition), ct.StaticServerSID, ) ct.Assert.HTTPStatus(t, staticServerWorkload, staticServerWorkload.Port, 200) - ct.Assert.AssertServiceHealth(t, cl, ct.StaticServerSID.Name, true, 1) + ct.Assert.HealthServiceEntries(t, cluster.Name, ct.StaticServerSID.Name, true, &api.QueryOptions{}, 1) staticClientWorkload := cluster.WorkloadByID( topology.NewNodeID("dc1-client2", defaultPartition), ct.StaticClientSID, ) - ct.Assert.AssertServiceHealth(t, cl, ct.StaticClientSID.Name, true, 1) + ct.Assert.HealthServiceEntries(t, cluster.Name, ct.StaticClientSID.Name, true, &api.QueryOptions{}, 1) // check the service exists in catalog svcs := cluster.WorkloadsByID(ct.StaticClientSID) diff --git a/testing/deployer/sprawl/sprawl.go b/testing/deployer/sprawl/sprawl.go index bb57e4b8c8..a6607a027d 100644 --- a/testing/deployer/sprawl/sprawl.go +++ b/testing/deployer/sprawl/sprawl.go @@ -100,6 +100,19 @@ func (s *Sprawl) HTTPClientForCluster(clusterName string) (*http.Client, error) return &http.Client{Transport: transport}, nil } +// LocalAddressForNode returns the local address for the given node in the cluster +func (s *Sprawl) LocalAddressForNode(clusterName string, nid topology.NodeID) (string, error) { + cluster, ok := s.topology.Clusters[clusterName] + if !ok { + return "", fmt.Errorf("no such cluster: %s", clusterName) + } + node := cluster.NodeByID(nid) + if !node.IsAgent() { + return "", fmt.Errorf("node is not an agent") + } + return node.LocalAddress(), nil +} + // APIClientForNode gets a pooled api.Client connected to the agent running on // the provided node. // @@ -138,7 +151,7 @@ func (s *Sprawl) APIClientForNode(clusterName string, nid topology.NodeID, token func (s *Sprawl) APIClientForCluster(clusterName, token string) (*api.Client, error) { clu := s.topology.Clusters[clusterName] // TODO: this always goes to the first client, but we might want to balance this - firstAgent := clu.FirstClient() + firstAgent := clu.FirstClient("") if firstAgent == nil { firstAgent = clu.FirstServer() } diff --git a/testing/deployer/topology/topology.go b/testing/deployer/topology/topology.go index 07c2bc84dd..79b3d468d4 100644 --- a/testing/deployer/topology/topology.go +++ b/testing/deployer/topology/topology.go @@ -376,12 +376,21 @@ func (c *Cluster) FirstServer() *Node { return nil } -func (c *Cluster) FirstClient() *Node { +// FirstClient returns the first client agent in the cluster. +// If segment is non-empty, it will return the first client agent in that segment. +func (c *Cluster) FirstClient(segment string) *Node { for _, node := range c.Nodes { if node.Kind != NodeKindClient || node.Disabled { continue } - return node + if segment == "" { + // return a client agent in default segment + return node + } else { + if node.Segment != nil && node.Segment.Name == segment { + return node + } + } } return nil }