Skip to content

Commit 065729c

Browse files
Merge pull request #265 from mxinden/refactor-e2e
test/e2e: Refactor framework setup & wait for query logic
2 parents 62bc89c + 651d920 commit 065729c

File tree

5 files changed

+268
-126
lines changed

5 files changed

+268
-126
lines changed

test/e2e/framework/framework.go

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -35,63 +35,76 @@ import (
3535
var namespaceName = "openshift-monitoring"
3636

3737
type Framework struct {
38-
OperatorClient *client.Client
39-
CRDClient crdc.CustomResourceDefinitionInterface
40-
KubeClient kubernetes.Interface
41-
OpenshiftRouteClient routev1.RouteV1Interface
38+
OperatorClient *client.Client
39+
CRDClient crdc.CustomResourceDefinitionInterface
40+
KubeClient kubernetes.Interface
41+
PrometheusK8sClient *PrometheusClient
4242

4343
MonitoringClient *monClient.MonitoringV1Client
4444
Ns string
4545
}
4646

47-
func New(kubeConfigPath string) (*Framework, error) {
47+
// New returns a new cluster monitoring operator end-to-end test framework and
48+
// triggers all the setup logic.
49+
func New(kubeConfigPath string) (*Framework, cleanUpFunc, error) {
4850
config, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath)
4951
if err != nil {
50-
return nil, err
52+
return nil, nil, err
5153
}
5254

5355
kubeClient, err := kubernetes.NewForConfig(config)
5456
if err != nil {
55-
return nil, errors.Wrap(err, "creating kubeClient failed")
57+
return nil, nil, errors.Wrap(err, "creating kubeClient failed")
5658
}
5759

60+
// So far only necessary for prometheusK8sClient.
5861
openshiftRouteClient, err := routev1.NewForConfig(config)
5962
if err != nil {
60-
return nil, errors.Wrap(err, "creating openshiftClient failed")
63+
return nil, nil, errors.Wrap(err, "creating openshiftClient failed")
6164
}
6265

6366
mClient, err := monClient.NewForConfig(config)
6467
if err != nil {
65-
return nil, errors.Wrap(err, "creating monitoring client failed")
68+
return nil, nil, errors.Wrap(err, "creating monitoring client failed")
6669
}
6770

6871
eclient, err := apiextensionsclient.NewForConfig(config)
6972
if err != nil {
70-
return nil, errors.Wrap(err, "creating extensions client failed")
73+
return nil, nil, errors.Wrap(err, "creating extensions client failed")
7174
}
7275
crdClient := eclient.ApiextensionsV1beta1().CustomResourceDefinitions()
7376

7477
operatorClient, err := client.New(config, "", namespaceName, "")
7578
if err != nil {
76-
return nil, errors.Wrap(err, "creating operator client failed")
79+
return nil, nil, errors.Wrap(err, "creating operator client failed")
7780
}
7881

7982
f := &Framework{
80-
OperatorClient: operatorClient,
81-
KubeClient: kubeClient,
82-
OpenshiftRouteClient: openshiftRouteClient,
83-
CRDClient: crdClient,
84-
MonitoringClient: mClient,
85-
Ns: namespaceName,
83+
OperatorClient: operatorClient,
84+
KubeClient: kubeClient,
85+
CRDClient: crdClient,
86+
MonitoringClient: mClient,
87+
Ns: namespaceName,
88+
}
89+
90+
cleanUp, err := f.setup()
91+
if err != nil {
92+
return nil, nil, errors.Wrap(err, "failed to setup test framework")
93+
}
94+
95+
// Prometheus client depends on setup above.
96+
f.PrometheusK8sClient, err = NewPrometheusClient(openshiftRouteClient, kubeClient)
97+
if err != nil {
98+
return nil, nil, errors.Wrap(err, "creating prometheusK8sClient failed")
8699
}
87100

88-
return f, nil
101+
return f, cleanUp, nil
89102
}
90103

91104
type cleanUpFunc func() error
92105

93-
// Setup creates everything necessary to use the test framework.
94-
func (f *Framework) Setup() (cleanUpFunc, error) {
106+
// setup creates everything necessary to use the test framework.
107+
func (f *Framework) setup() (cleanUpFunc, error) {
95108
cleanUpFuncs := []cleanUpFunc{}
96109

97110
cf, err := f.CreateServiceAccount()

test/e2e/framework/prometheus_client.go

Lines changed: 96 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@ package framework
1616

1717
import (
1818
"crypto/tls"
19+
"fmt"
1920
"io/ioutil"
2021
"net/http"
22+
"strconv"
2123
"strings"
24+
"testing"
25+
"time"
2226

2327
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/apimachinery/pkg/util/wait"
2429
"k8s.io/client-go/kubernetes"
2530

2631
routev1 "github.com/openshift/client-go/route/clientset/versioned/typed/route/v1"
@@ -37,7 +42,7 @@ type PrometheusClient struct {
3742
token string
3843
}
3944

40-
// NewPrometheusClient returns creates and returns a new PrometheusClient.
45+
// NewPrometheusClient creates and returns a new PrometheusClient.
4146
func NewPrometheusClient(
4247
routeClient routev1.RouteV1Interface,
4348
kubeClient kubernetes.Interface,
@@ -68,8 +73,9 @@ func NewPrometheusClient(
6873
}, nil
6974
}
7075

71-
// Query makes a request against the Prometheus /api/v1/query endpoint.
72-
func (c *PrometheusClient) Query(query string) (int, error) {
76+
// Query runs an http get request against the Prometheus query api and returns
77+
// the response body.
78+
func (c *PrometheusClient) Query(query string) ([]byte, error) {
7379
tr := &http.Transport{
7480
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
7581
}
@@ -78,7 +84,7 @@ func (c *PrometheusClient) Query(query string) (int, error) {
7884

7985
req, err := http.NewRequest("GET", "https://"+c.host+"/api/v1/query", nil)
8086
if err != nil {
81-
return 0, err
87+
return nil, err
8288
}
8389

8490
q := req.URL.Query()
@@ -89,21 +95,103 @@ func (c *PrometheusClient) Query(query string) (int, error) {
8995

9096
resp, err := client.Do(req)
9197
if err != nil {
92-
return 0, err
98+
return nil, err
9399
}
94100

95101
defer resp.Body.Close()
96102

97103
body, err := ioutil.ReadAll(resp.Body)
98104
if err != nil {
99-
return 0, err
105+
return nil, err
100106
}
101107

108+
return body, nil
109+
}
110+
111+
// GetFirstValueFromPromQuery takes a query api response body and returns the
112+
// value of the first timeseries. If body contains multiple timeseries
113+
// GetFirstValueFromPromQuery errors.
114+
func GetFirstValueFromPromQuery(body []byte) (int, error) {
102115
res, err := gabs.ParseJSON(body)
103116
if err != nil {
104117
return 0, err
105118
}
106119

107-
n, err := res.ArrayCountP("data.result")
108-
return n, err
120+
count, err := res.ArrayCountP("data.result")
121+
if err != nil {
122+
return 0, err
123+
}
124+
125+
if count != 1 {
126+
return 0, fmt.Errorf("expected body to contain single timeseries but got %v", count)
127+
}
128+
129+
timeseries, err := res.ArrayElementP(0, "data.result")
130+
if err != nil {
131+
return 0, err
132+
}
133+
134+
value, err := timeseries.ArrayElementP(1, "value")
135+
if err != nil {
136+
return 0, err
137+
}
138+
139+
v, err := strconv.Atoi(value.Data().(string))
140+
if err != nil {
141+
return 0, fmt.Errorf("failed to parse query value: %v", err)
142+
}
143+
144+
return v, nil
145+
}
146+
147+
// WaitForQueryReturnGreaterEqualOne see WaitForQueryReturn.
148+
func (c *PrometheusClient) WaitForQueryReturnGreaterEqualOne(t *testing.T, timeout time.Duration, query string) {
149+
c.WaitForQueryReturn(t, timeout, query, func(v int) error {
150+
if v >= 1 {
151+
return nil
152+
}
153+
154+
return fmt.Errorf("expected value to equal or greater than 1 but got %v", v)
155+
})
156+
}
157+
158+
// WaitForQueryReturnOne see WaitForQueryReturn.
159+
func (c *PrometheusClient) WaitForQueryReturnOne(t *testing.T, timeout time.Duration, query string) {
160+
c.WaitForQueryReturn(t, timeout, query, func(v int) error {
161+
if v == 1 {
162+
return nil
163+
}
164+
165+
return fmt.Errorf("expected value to equal 1 but got %v", v)
166+
})
167+
}
168+
169+
// WaitForQueryReturn waits for a given PromQL query for a given time interval
170+
// and validates the **first and only** result with the given validate function.
171+
func (c *PrometheusClient) WaitForQueryReturn(t *testing.T, timeout time.Duration, query string, validate func(int) error) {
172+
err := wait.Poll(5*time.Second, timeout, func() (bool, error) {
173+
defer t.Log("---------------------------\n")
174+
body, err := c.Query(query)
175+
if err != nil {
176+
return false, err
177+
}
178+
179+
v, err := GetFirstValueFromPromQuery(body)
180+
if err != nil {
181+
t.Logf("failed to extract first value from query response for query %q: %v", query, err)
182+
return false, nil
183+
}
184+
185+
if err := validate(v); err != nil {
186+
t.Logf("unexpected value for query %q: %v", query, err)
187+
return false, nil
188+
}
189+
190+
t.Logf("query %q succeeded", query)
191+
return true, nil
192+
})
193+
194+
if err != nil {
195+
t.Fatal(err)
196+
}
109197
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2019 The Cluster Monitoring Operator Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package framework
16+
17+
import (
18+
"testing"
19+
)
20+
21+
func TestGetFirstValueFromPromQuery(t *testing.T) {
22+
tests := []struct {
23+
Name string
24+
F func(t *testing.T)
25+
}{
26+
{
27+
Name: "should fail on multiple timeseries",
28+
F: func(t *testing.T) {
29+
body := `
30+
{"status":"success","data":{"resultType":"vector","result":[{"metric":{"__name__":"ALERTS","alertname":"TargetDown","alertstate":"firing","job":"metrics","severity":"warning"},"value":[1551102571.196,"1"]},{"metric":{"__name__":"ALERTS","alertname":"Watchdog","alertstate":"firing","severity":"none"},"value":[1551102571.196,"1"]}]}}
31+
`
32+
33+
_, err := GetFirstValueFromPromQuery([]byte(body))
34+
if err == nil || err.Error() != "expected body to contain single timeseries but got 2" {
35+
t.Fatalf("expected GetFirstValueFromPromQuery to fail on multiple timeseries but got err %q instead", err)
36+
}
37+
},
38+
},
39+
{
40+
Name: "should return first value",
41+
F: func(t *testing.T) {
42+
body := `
43+
{"status":"success","data":{"resultType":"vector","result":[{"metric":{"__name__":"ALERTS","alertname":"Watchdog","alertstate":"firing","severity":"none"},"value":[1551102571.196,"1"]}]}}
44+
`
45+
46+
v, err := GetFirstValueFromPromQuery([]byte(body))
47+
if err != nil {
48+
t.Fatal(err)
49+
}
50+
51+
if v != 1 {
52+
t.Fatalf("expected query to return %v but got %v", 1, v)
53+
}
54+
},
55+
},
56+
}
57+
58+
for _, test := range tests {
59+
t.Run(test.Name, test.F)
60+
}
61+
}

0 commit comments

Comments
 (0)