Consul is a distributed, highly available, and data center aware solution to connect and configure applications across dynamic, distributed infrastructure.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

215 lines
5.7 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package peering
import (
"fmt"
"net/http"
"net/url"
"testing"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/testing/deployer/topology"
"github.com/hashicorp/go-cleanhttp"
"github.com/stretchr/testify/require"
)
type ac4ProxyDefaultsSuite struct {
DC string
Peer string
nodeClient topology.NodeID
nodeServer topology.NodeID
serverSID topology.ID
clientSID topology.ID
upstream *topology.Destination
}
var ac4ProxyDefaultsSuites []sharedTopoSuite = []sharedTopoSuite{
&ac4ProxyDefaultsSuite{DC: "dc1", Peer: "dc2"},
&ac4ProxyDefaultsSuite{DC: "dc2", Peer: "dc1"},
}
func TestAC4ProxyDefaults(t *testing.T) {
runShareableSuites(t, ac4ProxyDefaultsSuites)
}
func (s *ac4ProxyDefaultsSuite) testName() string {
return fmt.Sprintf("ac4 proxy defaults %s->%s", s.DC, s.Peer)
}
// creates clients in s.DC and servers in s.Peer
func (s *ac4ProxyDefaultsSuite) setup(t *testing.T, ct *commonTopo) {
clu := ct.ClusterByDatacenter(t, s.DC)
peerClu := ct.ClusterByDatacenter(t, s.Peer)
partition := "default"
peer := LocalPeerName(peerClu, "default")
cluPeerName := LocalPeerName(clu, "default")
serverSID := topology.ID{
Name: "ac4-server-http",
Partition: partition,
}
// Define server as upstream for client
upstream := &topology.Destination{
ID: serverSID,
LocalPort: 5000,
Peer: peer,
}
// Make client which will dial server
clientSID := topology.ID{
Name: "ac4-http-client",
Partition: partition,
}
client := serviceExt{
Workload: NewFortioServiceWithDefaults(
clu.Datacenter,
clientSID,
func(s *topology.Workload) {
s.Destinations = []*topology.Destination{
upstream,
}
},
),
Config: &api.ServiceConfigEntry{
Kind: api.ServiceDefaults,
Name: clientSID.Name,
Partition: ConfigEntryPartition(clientSID.Partition),
Protocol: "http",
UpstreamConfig: &api.UpstreamConfiguration{
Defaults: &api.UpstreamConfig{
MeshGateway: api.MeshGatewayConfig{
Mode: api.MeshGatewayModeLocal,
},
},
},
},
}
clientNode := ct.AddServiceNode(clu, client)
server := serviceExt{
Workload: NewFortioServiceWithDefaults(
peerClu.Datacenter,
serverSID,
nil,
),
Config: &api.ServiceConfigEntry{
Kind: api.ServiceDefaults,
Name: serverSID.Name,
Partition: ConfigEntryPartition(serverSID.Partition),
Protocol: "http",
},
Exports: []api.ServiceConsumer{{Peer: cluPeerName}},
Intentions: &api.ServiceIntentionsConfigEntry{
Kind: api.ServiceIntentions,
Name: serverSID.Name,
Partition: ConfigEntryPartition(serverSID.Partition),
Sources: []*api.SourceIntention{
{
Name: client.ID.Name,
Peer: cluPeerName,
Action: api.IntentionActionAllow,
},
},
},
}
peerClu.InitialConfigEntries = append(peerClu.InitialConfigEntries,
&api.ProxyConfigEntry{
Kind: api.ProxyDefaults,
Name: api.ProxyConfigGlobal,
Partition: ConfigEntryPartition(server.ID.Partition),
Config: map[string]interface{}{
"protocol": "http",
"local_request_timeout_ms": 500,
},
MeshGateway: api.MeshGatewayConfig{
Mode: api.MeshGatewayModeLocal,
},
},
)
serverNode := ct.AddServiceNode(peerClu, server)
s.clientSID = clientSID
s.serverSID = serverSID
s.nodeServer = serverNode.ID()
s.nodeClient = clientNode.ID()
s.upstream = upstream
}
func (s *ac4ProxyDefaultsSuite) test(t *testing.T, ct *commonTopo) {
var client *topology.Workload
dc := ct.Sprawl.Topology().Clusters[s.DC]
peer := ct.Sprawl.Topology().Clusters[s.Peer]
clientSVC := dc.WorkloadByID(
s.nodeClient,
s.clientSID,
)
serverSVC := peer.WorkloadByID(
s.nodeServer,
s.serverSID,
)
// preconditions check
ct.Assert.HealthyWithPeer(t, dc.Name, serverSVC.ID, LocalPeerName(peer, "default"))
ct.Assert.FortioFetch2HeaderEcho(t, clientSVC, s.upstream)
t.Run("Validate services exist in catalog", func(t *testing.T) {
dcSvcs := dc.WorkloadsByID(s.clientSID)
require.Len(t, dcSvcs, 1, "expected exactly one client")
client = dcSvcs[0]
require.Len(t, client.Destinations, 1, "expected exactly one upstream for client")
server := dc.WorkloadsByID(s.serverSID)
require.Len(t, server, 1, "expected exactly one server")
require.Len(t, server[0].Destinations, 0, "expected no upstream for server")
})
t.Run("peered upstream exists in catalog", func(t *testing.T) {
ct.Assert.CatalogServiceExists(t, s.DC, s.upstream.ID.Name, &api.QueryOptions{
Peer: s.upstream.Peer,
})
})
t.Run("HTTP service fails due to connection timeout", func(t *testing.T) {
url504 := fmt.Sprintf("http://localhost:%d/fortio/fetch2?url=%s", client.ExposedPort(""),
url.QueryEscape(fmt.Sprintf("http://localhost:%d/?delay=1000ms", s.upstream.LocalPort)),
)
url200 := fmt.Sprintf("http://localhost:%d/fortio/fetch2?url=%s", client.ExposedPort(""),
url.QueryEscape(fmt.Sprintf("http://localhost:%d/", s.upstream.LocalPort)),
)
// validate request timeout error where service has 1000ms response delay and
// proxy default is set to local_request_timeout_ms: 500ms
// return 504
httpClient := cleanhttp.DefaultClient()
req, err := http.NewRequest(http.MethodGet, url504, nil)
require.NoError(t, err)
res, err := httpClient.Do(req)
require.NoError(t, err)
defer res.Body.Close()
require.Equal(t, http.StatusGatewayTimeout, res.StatusCode)
// validate successful GET request where service has no response delay and
// proxy default is set to local_request_timeout_ms: 500ms
// return 200
req, err = http.NewRequest(http.MethodGet, url200, nil)
require.NoError(t, err)
res, err = httpClient.Do(req)
require.NoError(t, err)
defer res.Body.Close()
require.Equal(t, http.StatusOK, res.StatusCode)
})
}