Skip to content

Commit de44227

Browse files
committed
ApiRequestCounts conditional gathering
1 parent 25b06fa commit de44227

9 files changed

+218
-15
lines changed

manifests/03-clusterrole.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,14 @@ rules:
212212
- get
213213
- list
214214
- watch
215+
- apiGroups:
216+
- apiserver.openshift.io
217+
resources:
218+
- apirequestcounts
219+
verbs:
220+
- get
221+
- list
222+
- watch
215223
---
216224
apiVersion: rbac.authorization.k8s.io/v1
217225
kind: ClusterRoleBinding

pkg/gatherers/conditional/conditional_gatherer.go

+38-8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
var gatheringFunctionBuilders = map[GatheringFunctionName]GathererFunctionBuilderPtr{
2828
GatherLogsOfNamespace: (*Gatherer).BuildGatherLogsOfNamespace,
2929
GatherImageStreamsOfNamespace: (*Gatherer).BuildGatherImageStreamsOfNamespace,
30+
GatherAPIRequestCounts: (*Gatherer).BuildGatherAPIRequestCounts,
3031
}
3132

3233
// gatheringRules contains all the rules used to run conditional gatherings.
@@ -77,6 +78,21 @@ var defaultGatheringRules = []GatheringRule{
7778
},
7879
},
7980
},
81+
{
82+
Conditions: []ConditionWithParams{
83+
{
84+
Type: AlertIsFiring,
85+
Params: AlertIsFiringConditionParams{
86+
Name: "APIRemovedInNextReleaseInUse",
87+
},
88+
},
89+
},
90+
GatheringFunctions: GatheringFunctions{
91+
GatherAPIRequestCounts: GatherAPIRequestCountsParams{
92+
AlertName: "APIRemovedInNextReleaseInUse",
93+
},
94+
},
95+
},
8096
}
8197

8298
const canConditionalGathererFail = false
@@ -86,8 +102,9 @@ type Gatherer struct {
86102
gatherProtoKubeConfig *rest.Config
87103
metricsGatherKubeConfig *rest.Config
88104
imageKubeConfig *rest.Config
89-
firingAlerts map[string]bool // golang doesn't have sets :(
90-
gatheringRules []GatheringRule
105+
// there can be multiple instances of the same alert
106+
firingAlerts map[string][]Alert // golang doesn't have sets :(
107+
gatheringRules []GatheringRule
91108
}
92109

93110
// New creates a new instance of conditional gatherer with the appropriate configs
@@ -138,7 +155,6 @@ func (g *Gatherer) GetGatheringFunctions(ctx context.Context) (map[string]gather
138155
if err != nil {
139156
return nil, err
140157
}
141-
142158
if allConditionsAreSatisfied {
143159
functions, errs := g.createGatheringClosures(conditionalGathering.GatheringFunctions)
144160
if len(errs) > 0 {
@@ -206,7 +222,7 @@ func (g *Gatherer) updateAlertsCache(ctx context.Context) error {
206222
func (g *Gatherer) updateAlertsCacheFromClient(ctx context.Context, metricsClient rest.Interface) error {
207223
const logPrefix = "conditional gatherer: "
208224

209-
g.firingAlerts = make(map[string]bool)
225+
g.firingAlerts = make(map[string][]Alert)
210226

211227
data, err := metricsClient.Get().AbsPath("federate").
212228
Param("match[]", `ALERTS{alertstate="firing"}`).
@@ -237,17 +253,31 @@ func (g *Gatherer) updateAlertsCacheFromClient(ctx context.Context, metricsClien
237253
klog.Info(logPrefix + "metric is nil")
238254
continue
239255
}
240-
256+
var alert Alert
241257
for _, label := range metric.GetLabel() {
242258
if label == nil {
243259
klog.Info(logPrefix + "label is nil")
244260
continue
245261
}
246-
247-
if label.GetName() == "alertname" {
248-
g.firingAlerts[label.GetValue()] = true
262+
switch label.GetName() {
263+
case "alertname":
264+
alert.Name = label.GetValue()
265+
case "version":
266+
alert.Version = label.GetValue()
267+
case "resource":
268+
alert.Resource = label.GetValue()
269+
case "group":
270+
alert.Group = label.GetValue()
249271
}
250272
}
273+
alerts, ok := g.firingAlerts[alert.Name]
274+
if ok {
275+
alerts = append(alerts, alert)
276+
} else {
277+
alerts = []Alert{alert}
278+
}
279+
280+
g.firingAlerts[alert.Name] = alerts
251281
}
252282

253283
return nil

pkg/gatherers/conditional/conditional_gatherer_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ func Test_Gatherer_GatherConditionalGathererRules(t *testing.T) {
166166
err = json.Unmarshal(item, &gotGatheringRules)
167167
assert.NoError(t, err)
168168

169-
assert.Len(t, gotGatheringRules, 1)
169+
assert.Len(t, gotGatheringRules, 2)
170170
}
171171

172172
func newFakeClientWithMetrics(metrics string) *fake.RESTClient {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package conditional
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/openshift/insights-operator/pkg/gatherers"
8+
"github.com/openshift/insights-operator/pkg/record"
9+
"github.com/openshift/insights-operator/pkg/utils"
10+
"k8s.io/apimachinery/pkg/api/errors"
11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
"k8s.io/apimachinery/pkg/runtime/schema"
13+
"k8s.io/client-go/dynamic"
14+
)
15+
16+
// APIRequestCount defines a type used when marshaling into JSON
17+
type APIRequestCount struct {
18+
ResourceName string `json:"resource"`
19+
RemovedInRelease string `json:"removed_in_release"`
20+
TotalRequestCount int64 `json:"total_request_count"`
21+
LastDayRequestCount int64 `json:"last_day_request_count"`
22+
}
23+
24+
// BuildGatherApiRequestCounts creates a gathering closure which collects logs api requests counts for the
25+
// resources mentioned in
26+
// Params is of type AlertIsFiringConditionParams:
27+
// - alert_name string - name of the firing alert
28+
//
29+
//
30+
// * Location in archive: conditional/api_request_counts.json
31+
// * Since versions:
32+
// * 4.10+
33+
func (g *Gatherer) BuildGatherAPIRequestCounts(paramsInterface interface{}) (gatherers.GatheringClosure, error) {
34+
params, ok := paramsInterface.(GatherAPIRequestCountsParams)
35+
if !ok {
36+
return gatherers.GatheringClosure{}, fmt.Errorf(
37+
"unexpected type in paramsInterface, expected %T, got %T",
38+
GatherAPIRequestCountsParams{}, paramsInterface,
39+
)
40+
}
41+
42+
return gatherers.GatheringClosure{
43+
Run: func(ctx context.Context) ([]record.Record, []error) {
44+
records, err := g.gatherAPIRequestCounts(ctx, params.AlertName)
45+
if err != nil {
46+
return records, err
47+
}
48+
return records, nil
49+
},
50+
CanFail: canConditionalGathererFail,
51+
}, nil
52+
}
53+
54+
func (g *Gatherer) gatherAPIRequestCounts(ctx context.Context, alertName string) ([]record.Record, []error) {
55+
var resources []string
56+
for _, a := range g.firingAlerts[alertName] {
57+
resourceName := fmt.Sprintf("%s.%s.%s", a.Resource, a.Version, a.Group)
58+
resources = append(resources, resourceName)
59+
}
60+
61+
dynamicClient, err := dynamic.NewForConfig(g.gatherProtoKubeConfig)
62+
if err != nil {
63+
return nil, []error{err}
64+
}
65+
gvr := schema.GroupVersionResource{Group: "apiserver.openshift.io", Version: "v1", Resource: "apirequestcounts"}
66+
apiReqCountsList, err := dynamicClient.Resource(gvr).List(ctx, metav1.ListOptions{})
67+
if errors.IsNotFound(err) {
68+
return nil, nil
69+
}
70+
if err != nil {
71+
return nil, []error{err}
72+
}
73+
var records []record.Record
74+
var errrs []error
75+
var apiReqCounts []APIRequestCount
76+
for i := range apiReqCountsList.Items {
77+
it := apiReqCountsList.Items[i]
78+
79+
// filter only resources we're interested in
80+
if utils.IsInSlice(it.GetName(), resources) {
81+
totalReqCount, err := utils.NestedInt64Wrapper(it.Object, "status", "requestCount")
82+
if err != nil {
83+
errrs = append(errrs, err)
84+
}
85+
lastDayReqCount, err := utils.NestedInt64Wrapper(it.Object, "status", "currentHour", "requestCount")
86+
if err != nil {
87+
errrs = append(errrs, err)
88+
}
89+
removedInRel, err := utils.NestedStringWrapper(it.Object, "status", "removedInRelease")
90+
if err != nil {
91+
errrs = append(errrs, err)
92+
}
93+
apiReqCount := APIRequestCount{
94+
TotalRequestCount: totalReqCount,
95+
LastDayRequestCount: lastDayReqCount,
96+
ResourceName: it.GetName(),
97+
RemovedInRelease: removedInRel,
98+
}
99+
apiReqCounts = append(apiReqCounts, apiReqCount)
100+
}
101+
}
102+
records = append(records, record.Record{
103+
Name: fmt.Sprintf("%v/api_request_counts", g.GetName()),
104+
Item: record.JSONMarshaller{Object: apiReqCounts},
105+
})
106+
return records, errrs
107+
}

pkg/gatherers/conditional/gathering_functions.go

+21-6
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,19 @@ type GatheringFunctions = map[GatheringFunctionName]interface{}
1313
// GatheringFunctionName defines functions of conditional gatherer
1414
type GatheringFunctionName string
1515

16-
// GatherLogsOfNamespace is a function collecting logs of the provided namespace.
17-
// See file gather_logs_of_namespace.go
18-
const GatherLogsOfNamespace GatheringFunctionName = "logs_of_namespace"
16+
const (
17+
// GatherLogsOfNamespace is a function collecting logs of the provided namespace.
18+
// See file gather_logs_of_namespace.go
19+
GatherLogsOfNamespace GatheringFunctionName = "logs_of_namespace"
1920

20-
// GatherImageStreamsOfNamespace is a function collecting image streams of the provided namespace.
21-
// See file gather_image_streams_of_namespace.go
22-
const GatherImageStreamsOfNamespace GatheringFunctionName = "image_streams_of_namespace"
21+
// GatherImageStreamsOfNamespace is a function collecting image streams of the provided namespace.
22+
// See file gather_image_streams_of_namespace.go
23+
GatherImageStreamsOfNamespace GatheringFunctionName = "image_streams_of_namespace"
24+
25+
// GatherAPIRequestCounts is a function collecting api request counts for the resources read
26+
// from the corresponding alert
27+
GatherAPIRequestCounts GatheringFunctionName = "api_request_counts"
28+
)
2329

2430
func (name GatheringFunctionName) NewParams(jsonParams []byte) (interface{}, error) {
2531
switch name {
@@ -31,6 +37,10 @@ func (name GatheringFunctionName) NewParams(jsonParams []byte) (interface{}, err
3137
var result GatherImageStreamsOfNamespaceParams
3238
err := json.Unmarshal(jsonParams, &result)
3339
return result, err
40+
case GatherAPIRequestCounts:
41+
var params GatherAPIRequestCountsParams
42+
err := json.Unmarshal(jsonParams, &params)
43+
return params, err
3444
}
3545
return nil, fmt.Errorf("unable to create params for %T: %v", name, name)
3646
}
@@ -50,3 +60,8 @@ type GatherImageStreamsOfNamespaceParams struct {
5060
// Namespace from which to collect image streams
5161
Namespace string `json:"namespace"`
5262
}
63+
64+
// GatherAPIRequestCountsParams defines parameters for api_request_counts gatherer
65+
type GatherAPIRequestCountsParams struct {
66+
AlertName string `json:"alert_name"`
67+
}

pkg/gatherers/conditional/gathering_rule.schema.json

+13
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,19 @@
120120
"pattern": "^openshift-[a-zA-Z0-9_.-]{1,128}$"
121121
}
122122
}
123+
},
124+
"^api_request_counts$": {
125+
"type": "object",
126+
"title": "GatherApiRequestCountsParams",
127+
"required": [
128+
"alert_name"
129+
],
130+
"properties": {
131+
"alert_name": {
132+
"type": "string",
133+
"title": "AlertName"
134+
}
135+
}
123136
}
124137
}
125138
}

pkg/gatherers/conditional/types.go

+8
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,11 @@ type GatheringRule struct {
1515

1616
// GathererFunctionBuilderPtr defines a pointer to a gatherer function builder
1717
type GathererFunctionBuilderPtr = func(*Gatherer, interface{}) (gatherers.GatheringClosure, error)
18+
19+
// Alert defines basic alert attributes (basically alert labels)
20+
type Alert struct {
21+
Name string
22+
Resource string
23+
Version string
24+
Group string
25+
}

pkg/utils/slices.go

+10
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,13 @@ func TakeLastNItemsFromByteArray(array []byte, desiredLength int) []byte {
4040

4141
return array
4242
}
43+
44+
// IsInSlice checks if string is in the slice of strings
45+
func IsInSlice(v string, a []string) bool {
46+
for _, s := range a {
47+
if s == v {
48+
return true
49+
}
50+
}
51+
return false
52+
}

pkg/utils/unstructerd_wrappers.go

+12
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ func NestedSliceWrapper(obj map[string]interface{}, fields ...string) ([]interfa
3131
return s, nil
3232
}
3333

34+
func NestedInt64Wrapper(obj map[string]interface{}, fields ...string) (int64, error) {
35+
i, ok, err := unstructured.NestedInt64(obj, fields...)
36+
if !ok {
37+
return 0, fmt.Errorf("can't find %s", formatSlice(fields...))
38+
}
39+
if err != nil {
40+
return 0, err
41+
}
42+
43+
return i, nil
44+
}
45+
3446
func formatSlice(s ...string) string {
3547
var str string
3648
for _, f := range s {

0 commit comments

Comments
 (0)