Skip to content

Commit 2a4d23a

Browse files
PeterYurkovichzhuje
authored andcommitted
refactor: NO-JIRA: Improved korrel8r library.
Preparing to allow multiple queries per node on the panel. - Make korrel8r/* modules into a stand-alone, re-usable library. - Add Type safe Query, URIRef classes: centralize & simplify parsing - Move query/url conversion logic from Node to new 'Domain' and sub-classes - Rename TroubleshootingPanel "Query" to "Search" to distinguish from individual korrel8r queries. - Replace NodeError with TypeError, follow error handling convention of built-in `URL` type. - New Constraint type, centralize time parsing and formatting. - Makefile: re-generate client code with --indent=2 for consistency. - Minor fixes
1 parent b03806c commit 2a4d23a

51 files changed

Lines changed: 1699 additions & 1668 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,5 @@ gen-client: web/src/korrel8r/client
7272

7373
# NOTE: copied from https://github.com/korrel8r/korrel8r/blob/main/pkg/rest/docs/swagger.json
7474
web/src/korrel8r/client: korrel8r/swagger.json
75-
cd web && npx openapi-typescript-codegen --input ../$< --output ../$@ --name Korrel8rClient
75+
cd web && npx openapi-typescript-codegen --indent 2 --input ../$< --output ../$@ --name Korrel8rClient
7676
@touch $@
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
{
2-
"Cannot display in console": "Cannot display in console",
32
"Correlation result was empty.": "Correlation result was empty.",
3+
"Date and Time Range": "Date and Time Range",
44
"Find related resources.": "Find related resources.",
55
"Focus": "Focus",
66
"Goal class: ": "Goal class: ",
77
"Hide Query": "Hide Query",
8-
"Korrel8 query selecting the starting points for correlation.": "Korrel8 query selecting the starting points for correlation.",
8+
"Korrel8 query selecting the starting points for correlation.": "Starting query",
99
"Korrel8r Error": "Korrel8r Error",
10+
"Logging Plugin Disabled": "Logging Plugin Disabled",
1011
"Neighbourhood depth: ": "Neighbourhood depth: ",
12+
"Netflow Plugin Disabled": "Netflow Plugin Disabled",
1113
"No Correlated Signals Found": "No Correlated Signals Found",
1214
"Open the Troubleshooting Panel": "Open the Troubleshooting Panel",
13-
"Plugin disabled: ": "Plugin disabled: ",
1415
"Query": "Query",
1516
"Re-calculate the correlation graph starting from resources on the current console page.": "Re-calculate the correlation graph starting from resources on the current console page.",
1617
"Request Failed": "Request Failed",
1718
"Show graph of connected classes up to the specified depth.": "Show graph of connected classes up to the specified depth.",
1819
"Show graph of paths to signals of the specified class.": "Show graph of paths to signals of the specified class.",
1920
"Show Query": "Show Query",
20-
"The current console page does not show resources that are supported for correlation.": "The current console page does not show resources that are supported for correlation.",
21+
"The current console page is not supported for correlation.": "The current console page is not supported for correlation.",
2122
"Troubleshooting": "Troubleshooting",
22-
"Troubleshooting Panel": "Troubleshooting Panel"
23+
"Troubleshooting Panel": "Troubleshooting Panel",
24+
"Unable to find Console Link": "Unable to find Console Link"
2325
}

web/src/URLSearchParams.d.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { AlertNode } from '../korrel8r/alert';
1+
import { URIRef, Query } from '../korrel8r/types';
2+
import { AlertDomain } from '../korrel8r/alert';
23

34
describe('AlertNode.fromURL', () => {
45
it.each([
@@ -18,30 +19,12 @@ describe('AlertNode.fromURL', () => {
1819
query:
1920
'alert:alert:{"alertname":"KubePodCrashLooping","container":"bad-deployment","namespace":"default","pod":"bad-pod"}',
2021
},
21-
])('converts $url', ({ url, query }) => expect(AlertNode.fromURL(url).toQuery()).toEqual(query));
22-
23-
it('Test url to query parsing removes extraneous query parameters', () => {
24-
const url =
25-
'monitoring/alerts/12345?prometheus=openshift-monitoring/k8s&severity=warning&alertname=KubePodCrashLooping&' +
26-
'container=bad-deployment&endpoint=https-main&job=kube-state-metrics&namespace=default&' +
27-
'pod=bad-deployment-000000000-00000&reason=CrashLoopBackOff&service=kube-state-metrics&uid=00000000-0000-0000-0000-000000000000';
28-
const expectedKorrel8rQuery =
29-
'alert:alert:{"severity":"warning","alertname":"KubePodCrashLooping","container":"bad-deployment",' +
30-
'"endpoint":"https-main","job":"kube-state-metrics","namespace":"default","pod":"bad-deployment-000000000-00000",' +
31-
'"reason":"CrashLoopBackOff","service":"kube-state-metrics","uid":"00000000-0000-0000-0000-000000000000"}';
32-
expect(AlertNode.fromURL(url)?.toQuery()).toEqual(expectedKorrel8rQuery);
33-
});
34-
35-
// Errors
36-
it.each([{ url: 'monitoring/alert', expected: 'Expected alert URL' }])(
37-
`$url throws`,
38-
({ url, expected }) => {
39-
expect(() => AlertNode.fromURL(url)).toThrow(expected);
40-
},
22+
])('converts $url', ({ url, query }) =>
23+
expect(new AlertDomain().linkToQuery(new URIRef(url))).toEqual(Query.parse(query)),
4124
);
4225
});
4326

44-
describe('AlertNode.fromQuery', () => {
27+
describe('AlertDomain.fromQuery', () => {
4528
it.each([
4629
{
4730
query:
@@ -50,23 +33,16 @@ describe('AlertNode.fromQuery', () => {
5033
},
5134
{ query: 'alert:alert:{}', url: 'monitoring/alerts' },
5235
])('converts $query', ({ url, query }) => {
53-
expect(AlertNode.fromQuery(query).toURL()).toEqual(url);
36+
expect(new AlertDomain().queryToLink(Query.parse(query))).toEqual(url);
5437
});
5538

5639
it('Query => URL => Query', () => {
5740
const query =
5841
'alert:alert:{"alertname":"KubePodCrashLooping","container":"bad-deployment","namespace":"default","pod":"bad-pod"}';
59-
const expectedKorrel8rURL =
42+
const got =
6043
'monitoring/alerts?alerts=alertname%3DKubePodCrashLooping%2Ccontainer%3Dbad-deployment%2Cnamespace%3Ddefault%2Cpod%3Dbad-pod';
61-
const actualKorrel8rURL = AlertNode.fromQuery(query)?.toURL();
62-
expect(actualKorrel8rURL).toEqual(expectedKorrel8rURL);
63-
expect(AlertNode.fromURL(actualKorrel8rURL)?.toQuery()).toEqual(query);
64-
});
65-
66-
it.each([
67-
{ query: 'alert:aler', expected: 'Expected alert query: alert:aler' },
68-
{ query: 'alert:alert:', expected: 'Expected alert query' },
69-
])('$query throws', ({ query, expected }) => {
70-
expect(() => AlertNode.fromQuery(query)).toThrow(expected);
44+
const want = new AlertDomain().queryToLink(Query.parse(query));
45+
expect(want).toEqual(got);
46+
expect(new AlertDomain().linkToQuery(new URIRef(want))).toEqual(Query.parse(query));
7147
});
7248
});
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Korrel8rNodeFactory } from '../korrel8r/node-factory';
2-
import { Constraint } from '../redux-actions';
1+
import { allDomains } from '../korrel8r/all-domains';
2+
import { Constraint, Query, URIRef } from '../korrel8r/types';
33

44
beforeAll(() => {
55
// Mock API discovery resources.
@@ -18,43 +18,42 @@ beforeAll(() => {
1818
window['SERVER_FLAGS'] = { consoleVersion: 'x.y.z' };
1919
});
2020

21-
const testdata = [
21+
it.each([
2222
{
23+
url: 'monitoring/alerts?alerts=alertname%3DKubePodCrashLooping%2Ccontainer%3Dbad-deployment%2Cnamespace%3Ddefault%2Cpod%3Dbad-deployment',
2324
query:
2425
'alert:alert:{"alertname":"KubePodCrashLooping","container":"bad-deployment","namespace":"default","pod":"bad-deployment"}',
25-
url: 'monitoring/alerts?alerts=alertname%3DKubePodCrashLooping%2Ccontainer%3Dbad-deployment%2Cnamespace%3Ddefault%2Cpod%3Dbad-deployment',
2626
constraint: {
2727
start: null,
2828
end: null,
29-
} as Constraint,
29+
},
3030
},
3131
{
32-
query: 'k8s:Pod.v1.:{"namespace":"default","name":"bad-deployment-000000000-00000"}',
3332
url: 'k8s/ns/default/pods/bad-deployment-000000000-00000',
33+
query: 'k8s:Pod.v1:{"namespace":"default","name":"bad-deployment-000000000-00000"}',
3434
constraint: {
3535
start: null,
3636
end: null,
37-
} as Constraint,
37+
},
3838
},
3939
{
40-
query: 'netflow:network:{SrcK8S_Type="Pod",SrcK8S_Namespace="myNamespace"}',
4140
url: `netflow-traffic?tenant=network&filters=${encodeURIComponent(
4241
'src_kind=Pod;src_namespace=myNamespace',
4342
)}&startTime=1742896800&endTime=1742940000`,
43+
query: 'netflow:network:{SrcK8S_Type="Pod",SrcK8S_Namespace="myNamespace"}',
4444
constraint: {
4545
start: '2025-03-25T10:00:00.000Z',
4646
end: '2025-03-25T22:00:00.000Z',
47-
} as Constraint,
47+
},
4848
},
4949
{
5050
url:
5151
`observe/traces/1599dfd76bc896101a9811857ae3c3c9?` +
52-
`namespace=openshift-tracing&name=platform&tenant=platform`,
52+
`namespace=openshift-tracing&name=platform&tenant=platform&start=1742896800000`,
5353
query: `trace:span:{trace:id="1599dfd76bc896101a9811857ae3c3c9"}`,
5454
constraint: {
55-
start: null,
56-
end: null,
57-
} as Constraint,
55+
start: '2025-03-25T10:00:00.000Z',
56+
},
5857
},
5958
{
6059
url: `monitoring/logs?q=${encodeURIComponent(
@@ -64,18 +63,14 @@ const testdata = [
6463
constraint: {
6564
start: '2025-03-25T10:00:00.000Z',
6665
end: '2025-03-25T22:00:00.000Z',
67-
} as Constraint,
66+
},
6867
},
69-
];
70-
71-
describe('Korrel8rNodeFactory.fromURL', () => {
72-
it.each(testdata)('converts $url', ({ url, query }) =>
73-
expect(Korrel8rNodeFactory.fromURL(url).toQuery()).toEqual(query),
74-
);
75-
});
76-
77-
describe('Korrel8rNodeFactory.fromQuery', () => {
78-
it.each(testdata)('converts $query', ({ url, query, constraint }) => {
79-
expect(Korrel8rNodeFactory.fromQuery(query, constraint).toURL()).toEqual(url);
80-
});
68+
{
69+
url: 'monitoring/logs?q=%7Bkubernetes_namespace_name%3D%22openshift-image-registry%22%7D%7Cjson%7Ckubernetes_labels_docker_registry%3D%22default%22&tenant=infrastructure',
70+
query:
71+
'log:infrastructure:{kubernetes_namespace_name="openshift-image-registry"}|json|kubernetes_labels_docker_registry="default"',
72+
},
73+
])('convert URL<=>link', ({ url, query, constraint }) => {
74+
expect(allDomains.linkToQuery(new URIRef(url))).toEqual(Query.parse(query));
75+
expect(allDomains.queryToLink(Query.parse(query), Constraint.fromAPI(constraint))).toEqual(url);
8176
});
Lines changed: 37 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { K8sNode } from '../korrel8r/k8s';
1+
import { URIRef, Query } from '../korrel8r/types';
2+
import { K8sDomain } from '../korrel8r/k8s';
3+
4+
const k8s = new K8sDomain();
25

36
beforeAll(() => {
47
// Mock API discovery resources.
@@ -75,85 +78,80 @@ describe('K8sNode.fromURL', () => {
7578
},
7679
{
7780
url: 'k8s/ns/default/pods/bad-deployment-000000000-00000',
78-
query: 'k8s:Pod.v1.:{"namespace":"default","name":"bad-deployment-000000000-00000"}',
81+
query: 'k8s:Pod.v1:{"namespace":"default","name":"bad-deployment-000000000-00000"}',
7982
},
8083
{
8184
url: 'k8s/ns/default/pods/bad-deployment-000000000-00000/events',
8285
query:
83-
'k8s:Event.v1.:{"fields":{"involvedObject.namespace":"default","involvedObject.name":"bad-deployment-000000000-00000","involvedObject.apiVersion":"v1","involvedObject.kind":"Pod"}}',
86+
'k8s:Event.v1:{"fields":{"involvedObject.namespace":"default","involvedObject.name":"bad-deployment-000000000-00000","involvedObject.apiVersion":"v1","involvedObject.kind":"Pod"}}',
8487
},
85-
{ url: `/k8s/ns/default/pods/foo`, query: `k8s:Pod.v1.:{"namespace":"default","name":"foo"}` },
86-
{ url: `/k8s/ns/default/pods`, query: `k8s:Pod.v1.:{"namespace":"default"}` },
87-
{ url: `/k8s/cluster/projects/foo`, query: `k8s:Namespace.v1.:{"name":"foo"}` },
88+
{ url: `/k8s/ns/default/pods/foo`, query: `k8s:Pod.v1:{"namespace":"default","name":"foo"}` },
89+
{ url: `/k8s/ns/default/pods`, query: `k8s:Pod.v1:{"namespace":"default"}` },
90+
{ url: `/k8s/cluster/projects/foo`, query: `k8s:Namespace.v1:{"name":"foo"}` },
8891
{
8992
url: `/k8s/ns/x/operators.coreos.com~v1alpha1~ClusterServiceVersion/y`,
9093
query: `k8s:ClusterServiceVersion.v1alpha1.operators.coreos.com:{"namespace":"x","name":"y"}`,
9194
},
92-
{ url: `/search/all-namespaces?kind=core~v1~Pod`, query: `k8s:Pod.v1.:{}` },
93-
{ url: `/k8s/all-namespaces/core~v1~Pod`, query: `k8s:Pod.v1.:{}` },
94-
{ url: `/k8s/cluster/nodes/oscar7`, query: `k8s:Node.v1.:{"name":"oscar7"}` },
95-
{ url: '/k8s/ns/netobserv/core~v1~Pod', query: 'k8s:Pod.v1.:{"namespace":"netobserv"}' },
96-
])('converts $url', ({ url, query }) => expect(K8sNode.fromURL(url).toQuery()).toEqual(query));
95+
{ url: `/search/all-namespaces?kind=core~v1~Pod`, query: `k8s:Pod.v1:{}` },
96+
{ url: `/k8s/all-namespaces/core~v1~Pod`, query: `k8s:Pod.v1:{}` },
97+
{ url: `/k8s/cluster/nodes/oscar7`, query: `k8s:Node.v1:{"name":"oscar7"}` },
98+
{ url: '/k8s/ns/netobserv/core~v1~Pod', query: 'k8s:Pod.v1:{"namespace":"netobserv"}' },
99+
])('converts $url', ({ url, query }) =>
100+
expect(k8s.linkToQuery(new URIRef(url))).toEqual(Query.parse(query)),
101+
);
97102
});
98103

99104
describe('K8sNode.fromQuery', () => {
100105
it.each([
101106
// Variations on query parameters.
102107
// Note "fields" are ignored.
103108
{
104-
query: 'k8s:Pod.v1.:{"namespace":"default","name":"bad-deployment-000000000-00000"}',
105-
url: 'k8s/ns/default/pods/bad-deployment-000000000-00000',
109+
query: 'k8s:Pod.v1:{"namespace":"default","name":"bad-deployment-000000000-00000"}',
110+
u: 'k8s/ns/default/pods/bad-deployment-000000000-00000',
106111
},
107112
{
108113
query: `k8s:Pod:{"namespace":"x","name":"y","labels":{"a":"b","c":"d"},"fields": {"x":"y"}}`,
109-
url: `k8s/ns/x/pods/y?labels=${encodeURIComponent('a=b,c=d')}`,
114+
u: `k8s/ns/x/pods/y?labels=${encodeURIComponent('a=b,c=d')}`,
110115
},
111116
{
112117
query:
113-
'k8s:Event.v1.:{"fields":{"involvedObject.namespace":"default","involvedObject.name":"bad-deployment-000000000-00000","involvedObject.apiVersion":"v1","involvedObject.kind":"Pod"}}',
114-
url: 'k8s/ns/default/pods/bad-deployment-000000000-00000/events',
118+
'k8s:Event.v1:{"fields":{"involvedObject.namespace":"default","involvedObject.name":"bad-deployment-000000000-00000","involvedObject.apiVersion":"v1","involvedObject.kind":"Pod"}}',
119+
u: 'k8s/ns/default/pods/bad-deployment-000000000-00000/events',
115120
},
116121
{
117122
query: `k8s:Pod:{"namespace":"x","name":"y","labels":{"a":"b"}}`,
118-
url: `k8s/ns/x/pods/y?labels=${encodeURIComponent('a=b')}`,
123+
u: `k8s/ns/x/pods/y?labels=${encodeURIComponent('a=b')}`,
119124
},
120125
{
121126
query: `k8s:Pod:{"namespace":"x","labels":{"a":"b"}}`,
122-
url: `k8s/ns/x/pods?labels=${encodeURIComponent('a=b')}`,
127+
u: `k8s/ns/x/pods?labels=${encodeURIComponent('a=b')}`,
123128
},
124-
{ query: `k8s:Pod.v1:{"namespace":"x","name":"y"}`, url: `k8s/ns/x/pods/y` },
125-
{ query: `k8s:Pod.v1:{"namespace":"x"}`, url: `k8s/ns/x/pods` },
129+
{ query: `k8s:Pod.v1:{"namespace":"x","name":"y"}`, u: `k8s/ns/x/pods/y` },
130+
{ query: `k8s:Pod.v1:{"namespace":"x"}`, u: `k8s/ns/x/pods` },
126131
{
127132
query: `k8s:Pod.v1:{"labels":{"a":"b"}}`,
128-
url: `search/all-namespaces?labels=${encodeURIComponent('a=b')}&kind=core~v1~Pod`,
133+
u: `search/all-namespaces?labels=${encodeURIComponent('a=b')}&kind=core~v1~Pod`,
129134
},
130135
{
131136
query: `k8s:Pod.v1:{"namespace":"x","labels":{"a":"b"}}`,
132-
url: `k8s/ns/x/pods?labels=${encodeURIComponent('a=b')}`,
137+
u: `k8s/ns/x/pods?labels=${encodeURIComponent('a=b')}`,
133138
},
134139

135140
// Variations on korrel8r class spec.
136-
{ query: `k8s:Role:{"namespace":"x", "name":"y"}`, url: `k8s/ns/x/roles/y` },
137-
{ query: `k8s:Role.:{"namespace":"x", "name":"y"}`, url: `k8s/ns/x/roles/y` },
138-
{ query: `k8s:Role.v1:{"namespace":"x", "name":"y"}`, url: `k8s/ns/x/roles/y` },
139-
{ query: `k8s:Role.v1.:{"namespace":"x", "name":"y"}`, url: `k8s/ns/x/roles/y` },
141+
{ query: `k8s:Role:{"namespace":"x", "name":"y"}`, u: `k8s/ns/x/roles/y` },
142+
{ query: `k8s:Role.:{"namespace":"x", "name":"y"}`, u: `k8s/ns/x/roles/y` },
143+
{ query: `k8s:Role.v1:{"namespace":"x", "name":"y"}`, u: `k8s/ns/x/roles/y` },
144+
{ query: `k8s:Role.v1:{"namespace":"x", "name":"y"}`, u: `k8s/ns/x/roles/y` },
140145
{
141146
query: `k8s:Role.v1.rbac.authorization.k8s.io:{"namespace":"x", "name":"y"}`,
142-
url: `k8s/ns/x/roles/y`,
147+
u: `k8s/ns/x/roles/y`,
143148
},
144-
{ query: `k8s:Pod:{}`, url: 'k8s/all-namespaces/pods' },
145-
])('converts $query to $url', ({ url, query }) => {
146-
expect(K8sNode.fromQuery(query).toURL()).toEqual(url);
149+
{ query: `k8s:Pod:{}`, u: 'k8s/all-namespaces/pods' },
150+
])('converts $query to $url', ({ u: u, query }) => {
151+
expect(k8s.queryToLink(Query.parse(query))).toEqual(u);
147152
});
148153

149-
it.each([
150-
{ query: `foo:bar:baz`, err: 'Expected k8s query' },
151-
{ query: `k8s:Pod`, err: 'Expected k8s query' },
152-
{ query: `k8s:nosuch:{}`, err: 'Unknown k8s kind:' },
153-
{ query: `k8s:Role.v1.bad.group:{}`, err: `Unknown k8s kind:` },
154-
])('raises error on $query', ({ query, err }) =>
155-
expect(() => {
156-
K8sNode.fromQuery(query);
157-
}).toThrow(err),
154+
it.each([{ query: `foo:bar:baz` }])('raises error on $query', ({ query }) =>
155+
expect(() => k8s.queryToLink(Query.parse(query))).toThrow(TypeError),
158156
);
159157
});

web/src/__tests__/korrel8r-url-parsing.spec.ts

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)