Skip to content

Commit b94b61c

Browse files
author
Ricardo Lüders
authored
feat: pod_definition conditional gather (#576)
* feat: pod_definition conditional gather * tests: adding unit tests to pod_definition gather * docs: adding sample data for pod_definition gather * docs: update gathered-data with pod_definition description * style: ignore lint for dupl - false positive * style: false nolint dupl * refactor: change pod_definition data path
1 parent 913b7b3 commit b94b61c

File tree

8 files changed

+886
-1
lines changed

8 files changed

+886
-1
lines changed

docs/gathered-data.md

+9
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,15 @@ Docs for relevant types: https://pkg.go.dev/github.com/openshift/api/operatorcon
673673
* 4.8+
674674

675675

676+
## PodDefinition
677+
678+
collects pod definition from pods that are firing one of the configured alerts.
679+
680+
* Location in archive: conditional/namespaces/<namespace>/pods/<pod>/<pod>.json
681+
* Since versions:
682+
* 4.11+
683+
684+
676685
## PodDisruptionBudgets
677686

678687
gathers the cluster's PodDisruptionBudgets.

docs/insights-archive-sample/conditional/namespaces/openshift-monitoring/pods/alertmanager-main-0/alertmanager-main-0.json

+668
Large diffs are not rendered by default.

pkg/gatherers/conditional/conditional_gatherer.go

+4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ var gatheringFunctionBuilders = map[GatheringFunctionName]GathererFunctionBuilde
3636
GatherImageStreamsOfNamespace: (*Gatherer).BuildGatherImageStreamsOfNamespace,
3737
GatherAPIRequestCounts: (*Gatherer).BuildGatherAPIRequestCounts,
3838
GatherContainersLogs: (*Gatherer).BuildGatherContainersLogs,
39+
GatherPodDefinition: (*Gatherer).BuildGatherPodDefinition,
3940
}
4041

4142
// gatheringRules contains all the rules used to run conditional gatherings.
@@ -136,6 +137,9 @@ var defaultGatheringRules = []GatheringRule{
136137
TailLines: 100,
137138
Previous: false,
138139
},
140+
GatherPodDefinition: GatherPodDefinitionParams{
141+
AlertName: "KubePodNotReady",
142+
},
139143
},
140144
},
141145
{

pkg/gatherers/conditional/gather_containers_logs.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
// * Id in config: containers_logs
2020
// * Since versions:
2121
// * 4.10+
22-
func (g *Gatherer) BuildGatherContainersLogs(paramsInterface interface{}) (gatherers.GatheringClosure, error) {
22+
func (g *Gatherer) BuildGatherContainersLogs(paramsInterface interface{}) (gatherers.GatheringClosure, error) { // nolint: dupl
2323
params, ok := paramsInterface.(GatherContainersLogsParams)
2424
if !ok {
2525
return gatherers.GatheringClosure{}, fmt.Errorf(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package conditional
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
"k8s.io/client-go/kubernetes"
9+
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
10+
"k8s.io/klog/v2"
11+
12+
"github.com/openshift/insights-operator/pkg/gatherers"
13+
"github.com/openshift/insights-operator/pkg/record"
14+
)
15+
16+
// BuildGatherPodDefinition collects pod definition from pods that are firing one of the configured alerts.
17+
//
18+
// * Location in archive: conditional/namespaces/<namespace>/pods/<pod>/<pod>.json
19+
// * Since versions:
20+
// * 4.11+
21+
func (g *Gatherer) BuildGatherPodDefinition(paramsInterface interface{}) (gatherers.GatheringClosure, error) { // nolint: dupl
22+
params, ok := paramsInterface.(GatherPodDefinitionParams)
23+
if !ok {
24+
return gatherers.GatheringClosure{}, fmt.Errorf(
25+
"unexpected type in paramsInterface, expected %T, got %T",
26+
GatherContainersLogsParams{},
27+
paramsInterface)
28+
}
29+
30+
return gatherers.GatheringClosure{
31+
Run: func(ctx context.Context) ([]record.Record, []error) {
32+
kubeClient, err := kubernetes.NewForConfig(g.gatherProtoKubeConfig)
33+
if err != nil {
34+
return nil, []error{err}
35+
}
36+
coreClient := kubeClient.CoreV1()
37+
return g.gatherPodDefinition(ctx, params, coreClient)
38+
},
39+
}, nil
40+
}
41+
42+
func (g *Gatherer) gatherPodDefinition(
43+
ctx context.Context,
44+
params GatherPodDefinitionParams,
45+
coreClient corev1client.CoreV1Interface,
46+
) ([]record.Record, []error) {
47+
alertInstances, ok := g.firingAlerts[params.AlertName]
48+
if !ok {
49+
err := fmt.Errorf("conditional gather triggered, but specified alert %q is not firing", params.AlertName)
50+
return nil, []error{err}
51+
}
52+
53+
const logMissingAlert = "%s at alertName: %s"
54+
55+
var errs []error
56+
var records []record.Record
57+
58+
for _, alertLabels := range alertInstances {
59+
podNamespace, err := getAlertPodNamespace(alertLabels)
60+
if err != nil {
61+
klog.Warningf(logMissingAlert, err.Error(), params.AlertName)
62+
errs = append(errs, err)
63+
continue
64+
}
65+
podName, err := getAlertPodName(alertLabels)
66+
if err != nil {
67+
klog.Warningf(logMissingAlert, err.Error(), params.AlertName)
68+
errs = append(errs, err)
69+
continue
70+
}
71+
72+
pod, err := coreClient.Pods(podNamespace).Get(ctx, podName, metav1.GetOptions{})
73+
if err != nil {
74+
klog.Warningf("pod %s not found in %s namespace: %w", podName, podNamespace, err)
75+
errs = append(errs, err)
76+
continue
77+
}
78+
79+
records = append(records, record.Record{
80+
Name: fmt.Sprintf(
81+
"%s/namespaces/%s/pods/%s/%s",
82+
g.GetName(),
83+
podNamespace,
84+
podName,
85+
podName),
86+
Item: record.ResourceMarshaller{Resource: pod},
87+
})
88+
}
89+
90+
return records, errs
91+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package conditional
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
corev1 "k8s.io/api/core/v1"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
kubefake "k8s.io/client-go/kubernetes/fake"
11+
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
12+
)
13+
14+
func TestGatherer_gatherPodDefinition(t *testing.T) {
15+
type fields struct {
16+
firingAlerts map[string][]AlertLabels
17+
}
18+
type args struct {
19+
ctx context.Context
20+
params GatherPodDefinitionParams
21+
coreClient func(context.Context) v1.CoreV1Interface
22+
}
23+
tests := []struct {
24+
name string
25+
args args
26+
fields fields
27+
wantLen int
28+
wantErr bool
29+
}{
30+
{
31+
name: "get pod definition",
32+
args: args{
33+
ctx: context.TODO(),
34+
params: GatherPodDefinitionParams{
35+
AlertName: "KubePodNotReady",
36+
},
37+
coreClient: func(ctx context.Context) v1.CoreV1Interface {
38+
coreClient := kubefake.NewSimpleClientset().CoreV1()
39+
_, err := coreClient.Pods("ns").Create(ctx, &corev1.Pod{
40+
ObjectMeta: metav1.ObjectMeta{
41+
Name: "test-pod",
42+
Namespace: "ns",
43+
},
44+
Status: corev1.PodStatus{
45+
Phase: corev1.PodPending,
46+
},
47+
Spec: corev1.PodSpec{},
48+
}, metav1.CreateOptions{})
49+
if err != nil {
50+
t.Fatalf("unable to create fake pod: %v", err)
51+
}
52+
return coreClient
53+
},
54+
},
55+
fields: fields{
56+
firingAlerts: map[string][]AlertLabels{
57+
"KubePodNotReady": {
58+
{
59+
"pod": "test-pod",
60+
"namespace": "ns",
61+
},
62+
},
63+
},
64+
},
65+
wantLen: 1,
66+
wantErr: false,
67+
},
68+
}
69+
for _, tt := range tests {
70+
tt := tt
71+
t.Run(tt.name, func(t *testing.T) {
72+
t.Parallel()
73+
g := &Gatherer{firingAlerts: tt.fields.firingAlerts}
74+
75+
coreClient := tt.args.coreClient(tt.args.ctx)
76+
got, gotErr := g.gatherPodDefinition(tt.args.ctx, tt.args.params, coreClient)
77+
78+
assert.Len(t, got, tt.wantLen)
79+
if tt.wantErr {
80+
assert.Len(t, gotErr, 1)
81+
} else {
82+
assert.Len(t, gotErr, 0)
83+
}
84+
})
85+
}
86+
}

pkg/gatherers/conditional/gathering_functions.go

+13
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ const (
3030
// GatherContainersLogs is a function that collects logs from pod's containers
3131
// See file gather_containers_logs.go
3232
GatherContainersLogs GatheringFunctionName = "containers_logs"
33+
34+
// GatherPodDefinition is a function that collects the pod definitions
35+
// See file gather_pod_definition.go
36+
GatherPodDefinition GatheringFunctionName = "pod_definition"
3337
)
3438

3539
func (name GatheringFunctionName) NewParams(jsonParams []byte) (interface{}, error) {
@@ -50,6 +54,10 @@ func (name GatheringFunctionName) NewParams(jsonParams []byte) (interface{}, err
5054
var params GatherContainersLogsParams
5155
err := json.Unmarshal(jsonParams, &params)
5256
return params, err
57+
case GatherPodDefinition:
58+
var params GatherPodDefinitionParams
59+
err := json.Unmarshal(jsonParams, &params)
60+
return params, err
5361
}
5462
return nil, fmt.Errorf("unable to create params for %T: %v", name, name)
5563
}
@@ -83,3 +91,8 @@ type GatherContainersLogsParams struct {
8391
TailLines int64 `json:"tail_lines"`
8492
Previous bool `json:"previous,omitempty"`
8593
}
94+
95+
// GatherPodDefinitionParams defines parameters for pod_definition gatherer
96+
type GatherPodDefinitionParams struct {
97+
AlertName string `json:"alert_name"`
98+
}

pkg/gatherers/conditional/gathering_rule.schema.json

+14
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,20 @@
203203
"title": "If true, the previous logs will be gathered instead of the current"
204204
}
205205
}
206+
},
207+
"^pod_definition$": {
208+
"type": "object",
209+
"title": "GatherPodDefinitionParams",
210+
"required": [
211+
"alert_name"
212+
],
213+
"properties": {
214+
"alert_name": {
215+
"type": "string",
216+
"title": "AlertName",
217+
"pattern": "^[a-zA-Z0-9_]{1,128}$"
218+
}
219+
}
206220
}
207221
}
208222
}

0 commit comments

Comments
 (0)