package assert import ( "fmt" "io" "net/http" "regexp" "strings" "testing" "time" "github.com/hashicorp/go-cleanhttp" "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" "github.com/stretchr/testify/assert" ) const ( defaultHTTPTimeout = 100 * time.Second defaultHTTPWait = defaultWait ) // CatalogServiceExists verifies the service name exists in the Consul catalog func CatalogServiceExists(t *testing.T, c *api.Client, svc string) { retry.Run(t, func(r *retry.R) { services, _, err := c.Catalog().Service(svc, "", nil) if err != nil { r.Fatal("error reading service data") } if len(services) == 0 { r.Fatal("did not find catalog entry for ", svc) } }) } // CatalogServiceExists verifies the node name exists in the Consul catalog func CatalogNodeExists(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") } if node == nil { r.Fatal("did not find node entry for", nodeName) } }) } // HTTPServiceEchoes verifies that a post to the given ip/port combination returns the data // in the response body. Optional path can be provided to differentiate requests. func HTTPServiceEchoes(t *testing.T, ip string, port int, path string) { const phrase = "hello" failer := func() *retry.Timer { return &retry.Timer{Timeout: defaultHTTPTimeout, Wait: defaultHTTPWait} } client := cleanhttp.DefaultClient() url := fmt.Sprintf("http://%s:%d", ip, port) if path != "" { url += "/" + path } retry.RunWith(failer(), t, func(r *retry.R) { t.Logf("making call to %s", url) reader := strings.NewReader(phrase) res, err := client.Post(url, "text/plain", reader) if err != nil { r.Fatal("could not make call to service ", url) } defer res.Body.Close() body, err := io.ReadAll(res.Body) if err != nil { r.Fatal("could not read response body ", url) } if !strings.Contains(string(body), phrase) { r.Fatal("received an incorrect response ", string(body)) } }) } // ServiceLogContains returns true if the service container has the target string in its logs func ServiceLogContains(t *testing.T, service libservice.Service, target string) bool { logs, err := service.GetLogs() require.NoError(t, err) return strings.Contains(logs, target) } // AssertFortioName asserts that the fortio service replying at urlbase/debug // has a `FORTIO_NAME` env variable set. This validates that the client is sending // traffic to the right envoy proxy. // // It retries with timeout defaultHTTPTimeout and wait defaultHTTPWait. func AssertFortioName(t *testing.T, urlbase string, name string) { t.Helper() var fortioNameRE = regexp.MustCompile(("\nFORTIO_NAME=(.+)\n")) var body []byte retry.RunWith(&retry.Timer{Timeout: defaultHTTPTimeout, Wait: defaultHTTPWait}, t, func(r *retry.R) { resp, err := http.Get(fmt.Sprintf("%s/debug?env=dump", urlbase)) if err != nil { r.Error(err) return } defer resp.Body.Close() body, err = io.ReadAll(resp.Body) require.NoError(t, err) }) m := fortioNameRE.FindStringSubmatch(string(body)) require.GreaterOrEqual(t, len(m), 2) assert.Equal(t, name, m[1]) } // AssertContainerState validates service container status func AssertContainerState(t *testing.T, service libservice.Service, state string) { containerStatus, err := service.GetStatus() require.NoError(t, err) require.Equal(t, containerStatus, state, fmt.Sprintf("Expected: %s. Got %s", containerStatus, state)) }