Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rules API: Add e2e test #198

Merged
merged 21 commits into from
Jan 26, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions test/e2e/configs.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type testType string

const (
metrics testType = "metrics"
rules testType = "rules"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make format should fix these rogue whitespaces 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weirdly if I run make format locally I get:

jessica@cupsofwonder:~/workspace/api|rules-add-e2e ⇒  make format
/home/jessica/go/bin/golangci-lint-v1.21.0 run --fix --enable-all -c .golangci.yml
WARN [runner/nolint] Found unknown linters in //nolint directives: cyclop, exhaustivestruct, goerr113, gomnd, intefacer, paralleltest, testpackage 

so somehow it did not give any more output than that and also did not change any of the files.

I then tried to run with my local golangci-lint (with the latest version - 1.43.0) and it found some issues in the code, so I think we may tackle this separately? I wonder if would be good to also update the golangci-lint version (with the latest version I get also some warnings of linters that are deprecated and we use them in the nolinter directive (e.g. WARN [runner/nolint] Found unknown linters in //nolint directives: intefacer)

Should I create a separate issue for that? Maybe makes sense to also link to this issue @matej-g already opened: #212
For now I run manually gofmt to this config file, so the whitespaces should be fixed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, shouldn't the lint step fail?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to be fixed now 🤷

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you mean the whitespaces or make format?
To fix the whitespaces I ran gofmt manually, but I thought would be fixed by running make format instead (which does not seem to be the case) :(

logs testType = "logs"
tenants testType = "tenants"
interactive testType = "interactive"
Expand All @@ -30,6 +31,7 @@ const (
configsContainerPath = dockerLocalSharedDir + "/" + configSharedDir

envMetricsName = "e2e_metrics_read_write"
envRulesAPIName = "e2e_rules_api"
envLogsName = "e2e_logs_read_write_tail"
envTenantsName = "e2e_tenants"
envInteractive = "e2e_interactive"
Expand Down Expand Up @@ -166,3 +168,55 @@ func createDexYAML(
)
testutil.Ok(t, err)
}

const rulesYAMLTpl = `
type: S3
config:
bucket: %s
endpoint: %s
access_key: %s
insecure: true
secret_key: %s
`

func createRulesYAML(
t *testing.T,
e e2e.Environment,
bucket, endpoint, accessKey, secretKey string,
) {
yamlContent := []byte(fmt.Sprintf(
rulesYAMLTpl,
bucket,
endpoint,
accessKey,
secretKey,
))

err := ioutil.WriteFile(
filepath.Join(e.SharedDir(), configSharedDir, "rules-objstore.yaml"),
yamlContent,
os.FileMode(0755),
)
testutil.Ok(t, err)
}

const recordingRuleYamlTpl = `
groups:
- name: example
rules:
- record: job:http_inprogress_requests:sum
expr: sum by (job) (http_inprogress_requests)
`

const alertingRuleYamlTpl = `
groups:
- name: example
rules:
- alert: HighRequestLatency
expr: job:request_latency_seconds:mean5m{job="myjob"} > 0.5
for: 10m
labels:
severity: page
annotations:
summary: High request latency
`
12 changes: 12 additions & 0 deletions test/e2e/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ func getContainerName(t *testing.T, tt testType, serviceName string) string {
return envLogsName + "-" + serviceName
case metrics:
return envMetricsName + "-" + serviceName
case rules:
return envRulesAPIName + "-" + serviceName
case tenants:
return envTenantsName + "-" + serviceName
case interactive:
Expand Down Expand Up @@ -116,3 +118,13 @@ func assertResponse(t *testing.T, response string, expected string) {
fmt.Sprintf("failed to assert that the response '%s' contains '%s'", response, expected),
)
}

type tokenRoundTripper struct {
rt http.RoundTripper
token string
}

func (rt *tokenRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
r.Header.Add("Authorization", "bearer "+rt.token)
return rt.rt.RoundTrip(r)
}
4 changes: 2 additions & 2 deletions test/e2e/interactive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ func TestInteractiveSetup(t *testing.T) {

prepareConfigsAndCerts(t, interactive, e)
_, token, rateLimiterAddr := startBaseServices(t, e, interactive)
readEndpoint, writeEndpoint, readExtEndpoint := startServicesForMetrics(t, e)
readEndpoint, writeEndpoint, rulesEndpoint, readExtEndpoint := startServicesForMetrics(t, e)
logsEndpoint, logsExtEndpoint := startServicesForLogs(t, e)

api, err := newObservatoriumAPIService(
e,
withMetricsEndpoints("http://"+readEndpoint, "http://"+writeEndpoint),
withMetricsEndpoints("http://"+readEndpoint, "http://"+writeEndpoint, "http://"+rulesEndpoint),
withLogsEndpoints("http://"+logsEndpoint),
withRateLimiter(rateLimiterAddr),
)
Expand Down
14 changes: 2 additions & 12 deletions test/e2e/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ func TestMetricsReadAndWrite(t *testing.T) {

prepareConfigsAndCerts(t, metrics, e)
_, token, rateLimiterAddr := startBaseServices(t, e, metrics)
readEndpoint, writeEndpoint, readExtEndpoint := startServicesForMetrics(t, e)
readEndpoint, writeEndpoint, rulesEndpoint, readExtEndpoint := startServicesForMetrics(t, e)

api, err := newObservatoriumAPIService(
e,
withMetricsEndpoints("http://"+readEndpoint, "http://"+writeEndpoint),
withMetricsEndpoints("http://"+readEndpoint, "http://"+writeEndpoint, "http://"+rulesEndpoint),
withRateLimiter(rateLimiterAddr),
)
testutil.Ok(t, err)
Expand Down Expand Up @@ -191,13 +191,3 @@ func TestMetricsReadAndWrite(t *testing.T) {
})
})
}

type tokenRoundTripper struct {
rt http.RoundTripper
token string
}

func (rt *tokenRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
r.Header.Add("Authorization", "bearer "+rt.token)
return rt.rt.RoundTrip(r)
}
123 changes: 123 additions & 0 deletions test/e2e/rules_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// +build integration

package e2e

import (
"bytes"
"io/ioutil"
"net/http"
"testing"

"github.com/efficientgo/e2e"
"github.com/efficientgo/tools/core/pkg/testutil"
)

func TestRulesAPI(t *testing.T) {
t.Parallel()

e, err := e2e.NewDockerEnvironment(envRulesAPIName)
testutil.Ok(t, err)
t.Cleanup(e.Close)

prepareConfigsAndCerts(t, rules, e)
_, token, rateLimiterAddr := startBaseServices(t, e, rules)
_, _, rulesEndpoint, _ := startServicesForMetrics(t, e) // TODO: create another function just for rules?
jessicalins marked this conversation as resolved.
Show resolved Hide resolved

api, err := newObservatoriumAPIService(
e,
withMetricsEndpoints("", "", "http://"+rulesEndpoint),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might need to provide some write/read endpoint, otherwise API will complain that neither metrics nor logs endpoints are provided?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for the test environment, since is using https://github.com/observatorium/api/blob/main/test/e2e/services.go#L224 this does not apply? It seems to append in case they are not empty: https://github.com/observatorium/api/blob/main/test/e2e/services.go#L250-L253 but no validation in case they are empty.

I also searched in the test output for this error message "Neither logging nor metrics endpoints are enabled. Specifying at least a logging or a metrics endpoint is mandatory" from https://github.com/observatorium/api/blob/main/main.go#L199-L202 but I haven't found any matches

withRateLimiter(rateLimiterAddr),
)
testutil.Ok(t, err)
testutil.Ok(t, e2e.StartAndWaitReady(api))

t.Run("rules", func(t *testing.T) {
rulesEndpointURL := "https://" + api.Endpoint("https") + "/api/metrics/v1/test-oidc/api/v1/rules/raw"
tr := &http.Transport{
TLSClientConfig: getTLSClientConfig(t, e),
}

client := &http.Client{
Transport: &tokenRoundTripper{rt: tr, token: token},
}

// Try to list rules
r, err := http.NewRequest(
http.MethodGet,
rulesEndpointURL,
nil,
)
testutil.Ok(t, err)

res, err := client.Do(r)
testutil.Ok(t, err)
testutil.Equals(t, res.StatusCode, http.StatusNotFound)

// Set a recording rule
recordingRule := []byte(recordingRuleYamlTpl)
r, err = http.NewRequest(
http.MethodPut,
rulesEndpointURL,
bytes.NewReader(recordingRule),
)
testutil.Ok(t, err)

res, err = client.Do(r)
testutil.Ok(t, err)
testutil.Equals(t, res.StatusCode, http.StatusOK)

// Check if recording rule is listed
r, err = http.NewRequest(
http.MethodGet,
rulesEndpointURL,
nil,
)
testutil.Ok(t, err)

res, err = client.Do(r)
defer res.Body.Close()

testutil.Ok(t, err)
testutil.Equals(t, res.StatusCode, http.StatusOK)

body, err := ioutil.ReadAll(res.Body)
bodyStr := string(body)

assertResponse(t, bodyStr, "sum by (job) (http_inprogress_requests)")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a good idea to check for tenant labels here? 🙂
With something like: assertResponse(t, bodyStr, "tenant_id: 1610b0c3-c509-4592-a256-a1871353dbfa") (id taken from here).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea! added here: 7d50a67


// Set alerting rule
alertingRule := []byte(alertingRuleYamlTpl)
r, err = http.NewRequest(
http.MethodPut,
rulesEndpointURL,
bytes.NewReader(alertingRule),
)
testutil.Ok(t, err)

res, err = client.Do(r)
testutil.Ok(t, err)
testutil.Equals(t, res.StatusCode, http.StatusOK)

// Check if recording rule and alerting rule are listed
r, err = http.NewRequest(
http.MethodGet,
rulesEndpointURL,
nil,
)
testutil.Ok(t, err)

res, err = client.Do(r)
defer res.Body.Close()

testutil.Ok(t, err)
testutil.Equals(t, res.StatusCode, http.StatusOK)

body, err = ioutil.ReadAll(res.Body)
bodyStr = string(body)
//TODO: check why this fails - shouldn't it join recording+alerting rules?
//assertResponse(t, bodyStr, "sum by (job) (http_inprogress_requests)")
assertResponse(t, bodyStr, "alert: HighRequestLatency")

// TODO: add another test case for thanos-ruler-syncer flow
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Missing newline

43 changes: 41 additions & 2 deletions test/e2e/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const (
dexImage = "dexidp/dex:v2.30.0"
opaImage = "openpolicyagent/opa:0.31.0"
gubernatorImage = "thrawn01/gubernator:1.0.0-rc.8"
rulesBackendImage = "quay.io/observatorium/rules-objstore:main-2021-11-22-cc68e25"
jessicalins marked this conversation as resolved.
Show resolved Hide resolved

logLevelError = "error"
logLevelDebug = "debug"
Expand All @@ -33,6 +34,7 @@ const (
func startServicesForMetrics(t *testing.T, e e2e.Environment) (
metricsReadEndpoint string,
metricsWriteEndpoint string,
metricsRulesEndpoint string,
metricsExtReadEndppoint string,
) {
thanosReceive := newThanosReceiveService(e)
Expand All @@ -42,10 +44,20 @@ func startServicesForMetrics(t *testing.T, e e2e.Environment) (
[]string{thanosReceive.InternalEndpoint("grpc")},
e2edb.WithImage(thanosImage),
)
testutil.Ok(t, e2e.StartAndWaitReady(thanosReceive, thanosQuery))

// Create S3 replacement for rules backend
bucket := "obs_rules_test"
m := e2edb.NewMinio(e, "rules-minio", bucket)
testutil.Ok(t, e2e.StartAndWaitReady(m))

createRulesYAML(t, e, bucket, m.InternalEndpoint(e2edb.AccessPortName), e2edb.MinioAccessKey, e2edb.MinioSecretKey)

rulesBackend := newRulesBackendService(e)
testutil.Ok(t, e2e.StartAndWaitReady(thanosReceive, thanosQuery, rulesBackend))

return thanosQuery.InternalEndpoint("http"),
thanosReceive.InternalEndpoint("remote_write"),
rulesBackend.InternalEndpoint("http"),
thanosQuery.Endpoint("http")
}

Expand Down Expand Up @@ -174,6 +186,27 @@ func newLokiService(e e2e.Environment) *e2e.InstrumentedRunnable {
)
}

func newRulesBackendService(e e2e.Environment) *e2e.InstrumentedRunnable {
ports := map[string]int{"http": 8080, "internal": 8081}

args := e2e.BuildArgs(map[string]string{
"-log.level": logLevelDebug,
jessicalins marked this conversation as resolved.
Show resolved Hide resolved
"-web.listen": ":" + strconv.Itoa(ports["http"]),
"-web.internal.listen": ":" + strconv.Itoa(ports["internal"]),
"-web.healthchecks.url": "http://127.0.0.1:" + strconv.Itoa(ports["http"]),
"-objstore.config-file": filepath.Join(configsContainerPath, "rules-objstore.yaml"),
})

return e2e.NewInstrumentedRunnable(e, "rules_objstore", ports, "internal").Init(
e2e.StartOptions{
Image: rulesBackendImage,
Command: e2e.NewCommand("", args...),
Readiness: e2e.NewHTTPReadinessProbe("internal", "/ready", 200, 200),
User: strconv.Itoa(os.Getuid()),
},
)
}

func newOPAService(e e2e.Environment) *e2e.InstrumentedRunnable {
ports := map[string]int{"http": 8181}

Expand All @@ -197,6 +230,7 @@ type apiOptions struct {
logsEndpoint string
metricsReadEndpoint string
metricsWriteEndpoint string
metricsRulesEndpoint string
ratelimiterAddr string
}

Expand All @@ -208,10 +242,11 @@ func withLogsEndpoints(endpoint string) apiOption {
}
}

func withMetricsEndpoints(readEndpoint string, writeEndpoint string) apiOption {
func withMetricsEndpoints(readEndpoint, writeEndpoint, rulesEndpoint string) apiOption {
return func(o *apiOptions) {
o.metricsReadEndpoint = readEndpoint
o.metricsWriteEndpoint = writeEndpoint
o.metricsRulesEndpoint = rulesEndpoint
}
}

Expand Down Expand Up @@ -252,6 +287,10 @@ func newObservatoriumAPIService(
args = append(args, "--metrics.write.endpoint="+opts.metricsWriteEndpoint)
}

if opts.metricsRulesEndpoint != "" {
args = append(args, "--metrics.rules.endpoint="+opts.metricsRulesEndpoint)
}

if opts.logsEndpoint != "" {
args = append(args, "--logs.read.endpoint="+opts.logsEndpoint)
args = append(args, "--logs.tail.endpoint="+opts.logsEndpoint)
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/tenants_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ func TestTenantsRetryAuthenticationProviderRegistration(t *testing.T) {

prepareConfigsAndCerts(t, tenants, e)
dex, _, _ := startBaseServices(t, e, tenants)
readEndpoint, writeEndpoint, _ := startServicesForMetrics(t, e)
readEndpoint, writeEndpoint, rulesEndpoint, _ := startServicesForMetrics(t, e)

// Start API with stopped Dex and observe retries.
dex.Stop()

api, err := newObservatoriumAPIService(
e,
withMetricsEndpoints("http://"+readEndpoint, "http://"+writeEndpoint),
withMetricsEndpoints("http://"+readEndpoint, "http://"+writeEndpoint, "http://"+rulesEndpoint),
)
testutil.Ok(t, err)
testutil.Ok(t, e2e.StartAndWaitReady(api))
Expand Down