pepr 0.33.0 → 0.34.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/cli/banner.d.ts +2 -0
- package/dist/cli/banner.d.ts.map +1 -0
- package/dist/cli/build.d.ts +19 -0
- package/dist/cli/build.d.ts.map +1 -0
- package/dist/cli/deploy.d.ts +3 -0
- package/dist/cli/deploy.d.ts.map +1 -0
- package/dist/cli/dev.d.ts +3 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/format.d.ts +9 -0
- package/dist/cli/format.d.ts.map +1 -0
- package/dist/cli/init/index.d.ts +3 -0
- package/dist/cli/init/index.d.ts.map +1 -0
- package/dist/cli/init/templates.d.ts +196 -0
- package/dist/cli/init/templates.d.ts.map +1 -0
- package/dist/cli/init/utils.d.ts +21 -0
- package/dist/cli/init/utils.d.ts.map +1 -0
- package/dist/cli/init/utils.test.d.ts +2 -0
- package/dist/cli/init/utils.test.d.ts.map +1 -0
- package/dist/cli/init/walkthrough.d.ts +8 -0
- package/dist/cli/init/walkthrough.d.ts.map +1 -0
- package/dist/cli/init/walkthrough.test.d.ts +2 -0
- package/dist/cli/init/walkthrough.test.d.ts.map +1 -0
- package/dist/cli/kfc.d.ts +3 -0
- package/dist/cli/kfc.d.ts.map +1 -0
- package/dist/cli/monitor.d.ts +3 -0
- package/dist/cli/monitor.d.ts.map +1 -0
- package/dist/cli/root.d.ts +5 -0
- package/dist/cli/root.d.ts.map +1 -0
- package/dist/cli/update.d.ts +3 -0
- package/dist/cli/update.d.ts.map +1 -0
- package/dist/cli/uuid.d.ts +3 -0
- package/dist/cli/uuid.d.ts.map +1 -0
- package/dist/cli.js +69 -39
- package/dist/controller.js +1 -2
- package/dist/fixtures/loader.d.ts +5 -0
- package/dist/fixtures/loader.d.ts.map +1 -0
- package/dist/lib/assets/helm.d.ts +1 -0
- package/dist/lib/assets/helm.d.ts.map +1 -1
- package/dist/lib/assets/helm.test.d.ts +2 -0
- package/dist/lib/assets/helm.test.d.ts.map +1 -0
- package/dist/lib/assets/index.d.ts.map +1 -1
- package/dist/lib/assets/pods.d.ts +3 -0
- package/dist/lib/assets/pods.d.ts.map +1 -1
- package/dist/lib/assets/pods.test.d.ts +2 -0
- package/dist/lib/assets/pods.test.d.ts.map +1 -0
- package/dist/lib/assets/yaml.d.ts.map +1 -1
- package/dist/lib/controller/store.d.ts.map +1 -1
- package/dist/lib/errors.test.d.ts +2 -0
- package/dist/lib/errors.test.d.ts.map +1 -0
- package/dist/lib/filter.test.d.ts +2 -0
- package/dist/lib/filter.test.d.ts.map +1 -0
- package/dist/lib/helpers.d.ts +0 -5
- package/dist/lib/helpers.d.ts.map +1 -1
- package/dist/lib/helpers.test.d.ts +2 -0
- package/dist/lib/helpers.test.d.ts.map +1 -0
- package/dist/lib/included-files.test.d.ts +2 -0
- package/dist/lib/included-files.test.d.ts.map +1 -0
- package/dist/lib/logger.test.d.ts +2 -0
- package/dist/lib/logger.test.d.ts.map +1 -0
- package/dist/lib/metrics.d.ts +18 -0
- package/dist/lib/metrics.d.ts.map +1 -1
- package/dist/lib/metrics.test.d.ts +2 -0
- package/dist/lib/metrics.test.d.ts.map +1 -0
- package/dist/lib/module.test.d.ts +2 -0
- package/dist/lib/module.test.d.ts.map +1 -0
- package/dist/lib/mutate-request.test.d.ts +2 -0
- package/dist/lib/mutate-request.test.d.ts.map +1 -0
- package/dist/lib/queue.test.d.ts +2 -0
- package/dist/lib/queue.test.d.ts.map +1 -0
- package/dist/lib/schedule.test.d.ts +15 -0
- package/dist/lib/schedule.test.d.ts.map +1 -0
- package/dist/lib/storage.d.ts +1 -0
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.test.d.ts +2 -0
- package/dist/lib/storage.test.d.ts.map +1 -0
- package/dist/lib/tls.test.d.ts +2 -0
- package/dist/lib/tls.test.d.ts.map +1 -0
- package/dist/lib/utils.test.d.ts +2 -0
- package/dist/lib/utils.test.d.ts.map +1 -0
- package/dist/lib/validate-request.test.d.ts +2 -0
- package/dist/lib/validate-request.test.d.ts.map +1 -0
- package/dist/lib/watch-processor.d.ts.map +1 -1
- package/dist/lib/watch-processor.test.d.ts +2 -0
- package/dist/lib/watch-processor.test.d.ts.map +1 -0
- package/dist/lib.js +86 -24
- package/dist/lib.js.map +3 -3
- package/dist/sdk/sdk.test.d.ts +2 -0
- package/dist/sdk/sdk.test.d.ts.map +1 -0
- package/package.json +22 -16
- package/src/cli/banner.ts +63 -0
- package/src/cli/build.ts +370 -0
- package/src/cli/deploy.ts +105 -0
- package/src/cli/dev.ts +118 -0
- package/src/cli/format.ts +83 -0
- package/src/cli/init/index.ts +99 -0
- package/src/cli/init/templates.ts +124 -0
- package/src/cli/init/utils.test.ts +28 -0
- package/src/cli/init/utils.ts +55 -0
- package/src/cli/init/walkthrough.test.ts +21 -0
- package/src/cli/init/walkthrough.ts +96 -0
- package/src/cli/kfc.ts +45 -0
- package/src/cli/monitor.ts +101 -0
- package/src/cli/root.ts +12 -0
- package/src/cli/update.ts +95 -0
- package/src/cli/uuid.ts +44 -0
- package/src/fixtures/data/create-pod.json +271 -0
- package/src/fixtures/data/delete-pod.json +271 -0
- package/src/fixtures/loader.ts +18 -0
- package/src/lib/.prettierrc +14 -0
- package/src/lib/assets/helm.test.ts +64 -0
- package/src/lib/assets/helm.ts +35 -0
- package/src/lib/assets/index.ts +5 -1
- package/src/lib/assets/pods.test.ts +553 -0
- package/src/lib/assets/pods.ts +14 -6
- package/src/lib/assets/yaml.ts +15 -15
- package/src/lib/controller/index.ts +2 -2
- package/src/lib/controller/store.ts +0 -2
- package/src/lib/errors.test.ts +85 -0
- package/src/lib/filter.test.ts +384 -0
- package/src/lib/helpers.test.ts +1192 -0
- package/src/lib/helpers.ts +0 -17
- package/src/lib/included-files.test.ts +22 -0
- package/src/lib/logger.test.ts +18 -0
- package/src/lib/metrics.test.ts +132 -0
- package/src/lib/metrics.ts +68 -6
- package/src/lib/module.test.ts +126 -0
- package/src/lib/mutate-request.test.ts +188 -0
- package/src/lib/queue.test.ts +58 -0
- package/src/lib/schedule.test.ts +217 -0
- package/src/lib/storage.test.ts +216 -0
- package/src/lib/storage.ts +12 -4
- package/src/lib/tls.test.ts +18 -0
- package/src/lib/utils.test.ts +69 -0
- package/src/lib/validate-request.test.ts +124 -0
- package/src/lib/watch-processor.test.ts +322 -0
- package/src/lib/watch-processor.ts +20 -4
- package/src/sdk/sdk.test.ts +243 -0
- package/src/templates/.eslintrc.json +6 -0
- package/src/templates/capabilities/hello-pepr.ts +26 -11
- package/.prettierignore +0 -1
- package/CODE_OF_CONDUCT.md +0 -133
- package/SECURITY.md +0 -18
- package/SUPPORT.md +0 -16
- package/codecov.yaml +0 -19
- package/commitlint.config.js +0 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { beforeEach, describe, expect, it } from "@jest/globals";
|
|
5
|
+
import { KubernetesObject } from "kubernetes-fluent-client";
|
|
6
|
+
|
|
7
|
+
import { Operation, AdmissionRequest } from "./k8s";
|
|
8
|
+
import { ValidateActionResponse } from "./types";
|
|
9
|
+
import { PeprValidateRequest } from "./validate-request";
|
|
10
|
+
|
|
11
|
+
describe("PeprValidateRequest", () => {
|
|
12
|
+
let mockRequest: AdmissionRequest<KubernetesObject>;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
mockRequest = {
|
|
16
|
+
operation: Operation.CREATE,
|
|
17
|
+
uid: "test-uid",
|
|
18
|
+
kind: {
|
|
19
|
+
group: "",
|
|
20
|
+
version: "v1",
|
|
21
|
+
kind: "Pod",
|
|
22
|
+
},
|
|
23
|
+
resource: {
|
|
24
|
+
group: "",
|
|
25
|
+
version: "v1",
|
|
26
|
+
resource: "pods",
|
|
27
|
+
},
|
|
28
|
+
name: "test-pod",
|
|
29
|
+
userInfo: {
|
|
30
|
+
username: "test-user",
|
|
31
|
+
groups: ["test-group"],
|
|
32
|
+
},
|
|
33
|
+
object: {
|
|
34
|
+
apiVersion: "v1",
|
|
35
|
+
kind: "Pod",
|
|
36
|
+
metadata: {
|
|
37
|
+
name: "test-pod",
|
|
38
|
+
labels: {
|
|
39
|
+
"test-label": "true",
|
|
40
|
+
},
|
|
41
|
+
annotations: {
|
|
42
|
+
"test-annotation": "true",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should initialize correctly for non-DELETE operations", () => {
|
|
50
|
+
const wrapper = new PeprValidateRequest(mockRequest);
|
|
51
|
+
expect(wrapper.Raw).toEqual(mockRequest.object);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should initialize correctly for DELETE operations", () => {
|
|
55
|
+
mockRequest = {
|
|
56
|
+
...mockRequest,
|
|
57
|
+
operation: Operation.DELETE,
|
|
58
|
+
oldObject: {
|
|
59
|
+
apiVersion: "v1",
|
|
60
|
+
kind: "Pod",
|
|
61
|
+
metadata: {
|
|
62
|
+
name: "test-pod",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const wrapper = new PeprValidateRequest(mockRequest);
|
|
68
|
+
expect(wrapper.Raw).toEqual(mockRequest.oldObject);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should provide access to old resource", () => {
|
|
72
|
+
mockRequest = {
|
|
73
|
+
...mockRequest,
|
|
74
|
+
oldObject: {
|
|
75
|
+
apiVersion: "v1",
|
|
76
|
+
kind: "Pod",
|
|
77
|
+
metadata: {
|
|
78
|
+
name: "old-test-pod",
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
const wrapper = new PeprValidateRequest(mockRequest);
|
|
83
|
+
expect(wrapper.OldResource).toEqual(mockRequest.oldObject);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should provide access to the request object", () => {
|
|
87
|
+
const wrapper = new PeprValidateRequest(mockRequest);
|
|
88
|
+
expect(wrapper.Request).toEqual(mockRequest);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should check if a label exists", () => {
|
|
92
|
+
const wrapper = new PeprValidateRequest(mockRequest);
|
|
93
|
+
expect(wrapper.HasLabel("test-label")).toBeTruthy();
|
|
94
|
+
expect(wrapper.HasLabel("non-existent-label")).toBeFalsy();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should check if an annotation exists", () => {
|
|
98
|
+
const wrapper = new PeprValidateRequest(mockRequest);
|
|
99
|
+
expect(wrapper.HasAnnotation("test-annotation")).toBeTruthy();
|
|
100
|
+
expect(wrapper.HasAnnotation("non-existent-annotation")).toBeFalsy();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("should create an approval response", () => {
|
|
104
|
+
const wrapper = new PeprValidateRequest(mockRequest);
|
|
105
|
+
const response: ValidateActionResponse = wrapper.Approve();
|
|
106
|
+
expect(response).toEqual({ allowed: true });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should create a denial response", () => {
|
|
110
|
+
const wrapper = new PeprValidateRequest(mockRequest);
|
|
111
|
+
const response: ValidateActionResponse = wrapper.Deny("Not allowed", 403);
|
|
112
|
+
expect(response).toEqual({ allowed: false, statusMessage: "Not allowed", statusCode: 403 });
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should throw an error if the request object is not available", () => {
|
|
116
|
+
mockRequest = {
|
|
117
|
+
...mockRequest,
|
|
118
|
+
object: undefined as unknown as KubernetesObject,
|
|
119
|
+
};
|
|
120
|
+
expect(() => new PeprValidateRequest(mockRequest)).toThrowError(
|
|
121
|
+
"unable to load the request object into PeprRequest.Raw",
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
|
4
|
+
import { GenericClass, K8s, KubernetesObject, kind } from "kubernetes-fluent-client";
|
|
5
|
+
import { K8sInit, WatchPhase } from "kubernetes-fluent-client/dist/fluent/types";
|
|
6
|
+
import { WatchCfg, WatchEvent, Watcher } from "kubernetes-fluent-client/dist/fluent/watch";
|
|
7
|
+
import { Capability } from "./capability";
|
|
8
|
+
import { setupWatch, logEvent } from "./watch-processor";
|
|
9
|
+
import Log from "./logger";
|
|
10
|
+
import { metricsCollector } from "./metrics";
|
|
11
|
+
|
|
12
|
+
type onCallback = (eventName: string | symbol, listener: (msg: string) => void) => void;
|
|
13
|
+
|
|
14
|
+
// Mock the dependencies
|
|
15
|
+
jest.mock("kubernetes-fluent-client");
|
|
16
|
+
|
|
17
|
+
jest.mock("./logger", () => ({
|
|
18
|
+
debug: jest.fn(),
|
|
19
|
+
error: jest.fn(),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
jest.mock("./metrics", () => ({
|
|
23
|
+
metricsCollector: {
|
|
24
|
+
initCacheMissWindow: jest.fn(),
|
|
25
|
+
incCacheMiss: jest.fn(),
|
|
26
|
+
incRetryCount: jest.fn(),
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
describe("WatchProcessor", () => {
|
|
31
|
+
const mockStart = jest.fn();
|
|
32
|
+
const mockK8s = jest.mocked(K8s);
|
|
33
|
+
const mockApply = jest.fn();
|
|
34
|
+
const mockGet = jest.fn();
|
|
35
|
+
const mockWatch = jest.fn();
|
|
36
|
+
const mockEvents = jest.fn() as jest.MockedFunction<onCallback>;
|
|
37
|
+
|
|
38
|
+
const capabilities = [
|
|
39
|
+
{
|
|
40
|
+
bindings: [
|
|
41
|
+
{
|
|
42
|
+
isWatch: true,
|
|
43
|
+
model: "someModel",
|
|
44
|
+
filters: {},
|
|
45
|
+
event: "Create",
|
|
46
|
+
watchCallback: () => {
|
|
47
|
+
console.log("words");
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
] as unknown as Capability[];
|
|
53
|
+
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
jest.resetAllMocks();
|
|
56
|
+
jest.useFakeTimers();
|
|
57
|
+
|
|
58
|
+
mockK8s.mockImplementation(<T extends GenericClass, K extends KubernetesObject>() => {
|
|
59
|
+
return {
|
|
60
|
+
Apply: mockApply,
|
|
61
|
+
InNamespace: jest.fn().mockReturnThis(),
|
|
62
|
+
Watch: mockWatch,
|
|
63
|
+
Get: mockGet,
|
|
64
|
+
} as unknown as K8sInit<T, K>;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
mockWatch.mockImplementation(() => {
|
|
68
|
+
return {
|
|
69
|
+
start: mockStart,
|
|
70
|
+
events: {
|
|
71
|
+
on: mockEvents,
|
|
72
|
+
},
|
|
73
|
+
} as unknown as Watcher<typeof kind.Pod>;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
mockGet.mockImplementation(() => ({
|
|
77
|
+
data: {
|
|
78
|
+
"42dae115ed-8aa1f3": "756",
|
|
79
|
+
"8aa1fde099-32a12": "750",
|
|
80
|
+
},
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
mockApply.mockImplementation(() => Promise.resolve());
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should setup watches for all bindings with isWatch=true", async () => {
|
|
87
|
+
const watchCfg: WatchCfg = {
|
|
88
|
+
resyncFailureMax: 5,
|
|
89
|
+
resyncDelaySec: 5,
|
|
90
|
+
lastSeenLimitSeconds: 300,
|
|
91
|
+
relistIntervalSec: 1800,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
capabilities.push({
|
|
95
|
+
bindings: [
|
|
96
|
+
{ isWatch: true, model: "someModel", filters: { name: "bleh" }, event: "Create", watchCallback: jest.fn() },
|
|
97
|
+
{ isWatch: false, model: "someModel", filters: {}, event: "Create", watchCallback: jest.fn() },
|
|
98
|
+
],
|
|
99
|
+
} as unknown as Capability);
|
|
100
|
+
|
|
101
|
+
setupWatch(capabilities);
|
|
102
|
+
|
|
103
|
+
expect(mockK8s).toHaveBeenCalledTimes(2);
|
|
104
|
+
expect(mockK8s).toHaveBeenNthCalledWith(1, "someModel", {});
|
|
105
|
+
expect(mockK8s).toHaveBeenNthCalledWith(2, "someModel", { name: "bleh" });
|
|
106
|
+
|
|
107
|
+
expect(mockWatch).toHaveBeenCalledTimes(2);
|
|
108
|
+
expect(mockWatch).toHaveBeenCalledWith(expect.any(Function), expect.objectContaining(watchCfg));
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should not setup watches if capabilities array is empty", async () => {
|
|
112
|
+
await setupWatch([]);
|
|
113
|
+
expect(mockWatch).toHaveBeenCalledTimes(0);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should not setup watches if no bindings are present", async () => {
|
|
117
|
+
const capabilities = [{ bindings: [] }, { bindings: [] }] as unknown as Capability[];
|
|
118
|
+
await setupWatch(capabilities);
|
|
119
|
+
expect(mockWatch).toHaveBeenCalledTimes(0);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should exit if the watch fails to start", async () => {
|
|
123
|
+
const exitSpy = jest.spyOn(process, "exit").mockImplementation(() => {
|
|
124
|
+
return undefined as never;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
mockStart.mockRejectedValue(new Error("err") as never);
|
|
128
|
+
|
|
129
|
+
await setupWatch(capabilities);
|
|
130
|
+
|
|
131
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should watch for the give_up event", async () => {
|
|
135
|
+
const exitSpy = jest.spyOn(process, "exit").mockImplementation(() => {
|
|
136
|
+
return undefined as never;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
mockEvents.mockImplementation((eventName: string | symbol, listener: (msg: string) => void) => {
|
|
140
|
+
if (eventName === WatchEvent.GIVE_UP) {
|
|
141
|
+
expect(listener).toBeInstanceOf(Function);
|
|
142
|
+
listener("err");
|
|
143
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
setupWatch(capabilities);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("should setup watches with correct phases for different events", async () => {
|
|
151
|
+
const watchCallbackCreate = jest.fn();
|
|
152
|
+
const watchCallbackUpdate = jest.fn();
|
|
153
|
+
const watchCallbackDelete = jest.fn();
|
|
154
|
+
|
|
155
|
+
const capabilities = [
|
|
156
|
+
{
|
|
157
|
+
bindings: [
|
|
158
|
+
{ isWatch: true, model: "someModel", filters: {}, event: "Create", watchCallback: watchCallbackCreate },
|
|
159
|
+
{ isWatch: true, model: "someModel", filters: {}, event: "Update", watchCallback: watchCallbackUpdate },
|
|
160
|
+
{ isWatch: true, model: "someModel", filters: {}, event: "Delete", watchCallback: watchCallbackDelete },
|
|
161
|
+
// Add more events here
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
] as unknown as Capability[];
|
|
165
|
+
|
|
166
|
+
setupWatch(capabilities);
|
|
167
|
+
|
|
168
|
+
type mockArg = [(payload: kind.Pod, phase: WatchPhase) => void, WatchCfg];
|
|
169
|
+
|
|
170
|
+
const firstCall = mockWatch.mock.calls[0] as unknown as mockArg;
|
|
171
|
+
const secondCall = mockWatch.mock.calls[1] as unknown as mockArg;
|
|
172
|
+
const thirdCall = mockWatch.mock.calls[2] as unknown as mockArg;
|
|
173
|
+
|
|
174
|
+
expect(firstCall[1].resyncFailureMax).toEqual(5);
|
|
175
|
+
expect(firstCall[1].resyncDelaySec).toEqual(5);
|
|
176
|
+
expect(firstCall[0]).toBeInstanceOf(Function);
|
|
177
|
+
|
|
178
|
+
firstCall[0]({} as kind.Pod, WatchPhase.Added);
|
|
179
|
+
expect(watchCallbackCreate).toHaveBeenCalledTimes(1);
|
|
180
|
+
expect(watchCallbackCreate).toHaveBeenCalledWith({}, WatchPhase.Added);
|
|
181
|
+
|
|
182
|
+
firstCall[0]({} as kind.Pod, WatchPhase.Modified);
|
|
183
|
+
firstCall[0]({} as kind.Pod, WatchPhase.Deleted);
|
|
184
|
+
expect(watchCallbackDelete).toHaveBeenCalledTimes(0);
|
|
185
|
+
expect(watchCallbackUpdate).toHaveBeenCalledTimes(0);
|
|
186
|
+
|
|
187
|
+
watchCallbackCreate.mockClear();
|
|
188
|
+
watchCallbackUpdate.mockClear();
|
|
189
|
+
watchCallbackDelete.mockClear();
|
|
190
|
+
|
|
191
|
+
secondCall[0]({} as kind.Pod, WatchPhase.Modified);
|
|
192
|
+
expect(watchCallbackUpdate).toHaveBeenCalledTimes(1);
|
|
193
|
+
expect(watchCallbackUpdate).toHaveBeenCalledWith({}, WatchPhase.Modified);
|
|
194
|
+
|
|
195
|
+
secondCall[0]({} as kind.Pod, WatchPhase.Added);
|
|
196
|
+
secondCall[0]({} as kind.Pod, WatchPhase.Deleted);
|
|
197
|
+
expect(watchCallbackCreate).toHaveBeenCalledTimes(0);
|
|
198
|
+
expect(watchCallbackDelete).toHaveBeenCalledTimes(0);
|
|
199
|
+
|
|
200
|
+
watchCallbackCreate.mockClear();
|
|
201
|
+
watchCallbackUpdate.mockClear();
|
|
202
|
+
watchCallbackDelete.mockClear();
|
|
203
|
+
|
|
204
|
+
thirdCall[0]({} as kind.Pod, WatchPhase.Deleted);
|
|
205
|
+
expect(watchCallbackDelete).toHaveBeenCalledTimes(1);
|
|
206
|
+
expect(watchCallbackDelete).toHaveBeenCalledWith({}, WatchPhase.Deleted);
|
|
207
|
+
|
|
208
|
+
thirdCall[0]({} as kind.Pod, WatchPhase.Added);
|
|
209
|
+
thirdCall[0]({} as kind.Pod, WatchPhase.Modified);
|
|
210
|
+
expect(watchCallbackCreate).toHaveBeenCalledTimes(0);
|
|
211
|
+
expect(watchCallbackUpdate).toHaveBeenCalledTimes(0);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("should call the metricsCollector methods on respective events", async () => {
|
|
215
|
+
const mockIncCacheMiss = metricsCollector.incCacheMiss;
|
|
216
|
+
const mockInitCacheMissWindow = metricsCollector.initCacheMissWindow;
|
|
217
|
+
const mockIncRetryCount = metricsCollector.incRetryCount;
|
|
218
|
+
|
|
219
|
+
const watchCallback = jest.fn();
|
|
220
|
+
const capabilities = [
|
|
221
|
+
{
|
|
222
|
+
bindings: [{ isWatch: true, model: "someModel", filters: {}, event: "Create", watchCallback: watchCallback }],
|
|
223
|
+
},
|
|
224
|
+
] as unknown as Capability[];
|
|
225
|
+
|
|
226
|
+
setupWatch(capabilities);
|
|
227
|
+
|
|
228
|
+
type mockArg = [(payload: kind.Pod, phase: WatchPhase) => void, WatchCfg];
|
|
229
|
+
|
|
230
|
+
const firstCall = mockWatch.mock.calls[0] as unknown as mockArg;
|
|
231
|
+
|
|
232
|
+
const cacheMissWindowName = "window-1";
|
|
233
|
+
const retryCount = "retry-1";
|
|
234
|
+
|
|
235
|
+
firstCall[0]({} as kind.Pod, WatchPhase.Added);
|
|
236
|
+
mockEvents.mock.calls.forEach(call => {
|
|
237
|
+
if (call[0] === WatchEvent.CACHE_MISS) {
|
|
238
|
+
call[1](cacheMissWindowName);
|
|
239
|
+
}
|
|
240
|
+
if (call[0] === WatchEvent.INIT_CACHE_MISS) {
|
|
241
|
+
call[1](cacheMissWindowName);
|
|
242
|
+
}
|
|
243
|
+
if (call[0] === WatchEvent.INC_RESYNC_FAILURE_COUNT) {
|
|
244
|
+
call[1](retryCount);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
expect(mockIncCacheMiss).toHaveBeenCalledWith(cacheMissWindowName);
|
|
249
|
+
expect(mockInitCacheMissWindow).toHaveBeenCalledWith(cacheMissWindowName);
|
|
250
|
+
expect(mockIncRetryCount).toHaveBeenCalledWith(retryCount);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("should call parseInt with process.env.PEPR_RELIST_INTERVAL_SECONDS", async () => {
|
|
254
|
+
const parseIntSpy = jest.spyOn(global, "parseInt");
|
|
255
|
+
|
|
256
|
+
process.env.PEPR_RELIST_INTERVAL_SECONDS = "1800";
|
|
257
|
+
process.env.PEPR_LAST_SEEN_LIMIT_SECONDS = "300";
|
|
258
|
+
process.env.PEPR_RESYNC_DELAY_SECONDS = "60";
|
|
259
|
+
process.env.PEPR_RESYNC_FAILURE_MAX = "5";
|
|
260
|
+
|
|
261
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
262
|
+
const watchCfg: WatchCfg = {
|
|
263
|
+
resyncFailureMax: parseInt(process.env.PEPR_RESYNC_FAILURE_MAX, 10),
|
|
264
|
+
resyncDelaySec: parseInt(process.env.PEPR_RESYNC_DELAY_SECONDS, 10),
|
|
265
|
+
lastSeenLimitSeconds: parseInt(process.env.PEPR_LAST_SEEN_LIMIT_SECONDS, 10),
|
|
266
|
+
relistIntervalSec: parseInt(process.env.PEPR_RELIST_INTERVAL_SECONDS, 10),
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
capabilities.push({
|
|
270
|
+
bindings: [
|
|
271
|
+
{ isWatch: true, model: "someModel", filters: { name: "bleh" }, event: "Create", watchCallback: jest.fn() },
|
|
272
|
+
{ isWatch: false, model: "someModel", filters: {}, event: "Create", watchCallback: jest.fn() },
|
|
273
|
+
],
|
|
274
|
+
} as unknown as Capability);
|
|
275
|
+
|
|
276
|
+
setupWatch(capabilities);
|
|
277
|
+
|
|
278
|
+
expect(parseIntSpy).toHaveBeenCalledWith("1800", 10);
|
|
279
|
+
expect(parseIntSpy).toHaveBeenCalledWith("300", 10);
|
|
280
|
+
expect(parseIntSpy).toHaveBeenCalledWith("60", 10);
|
|
281
|
+
expect(parseIntSpy).toHaveBeenCalledWith("5", 10);
|
|
282
|
+
parseIntSpy.mockRestore();
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe("logEvent function", () => {
|
|
287
|
+
it("should handle data events", () => {
|
|
288
|
+
const mockObj = { id: "123", type: "Pod" } as KubernetesObject;
|
|
289
|
+
const message = "Test message";
|
|
290
|
+
logEvent(WatchEvent.DATA, message, mockObj);
|
|
291
|
+
expect(Log.debug).toHaveBeenCalledWith(mockObj, `Watch event ${WatchEvent.DATA} received. ${message}.`);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("should handle CONNECT events", () => {
|
|
295
|
+
const url = "/api/v1/namespaces/default/pods?watch=true&resourceVersion=0";
|
|
296
|
+
logEvent(WatchEvent.CONNECT, url);
|
|
297
|
+
expect(Log.debug).toHaveBeenCalledWith(`Watch event ${WatchEvent.CONNECT} received. ${url}.`);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("should handle LIST_ERROR events", () => {
|
|
301
|
+
const message = "LIST_ERROR";
|
|
302
|
+
logEvent(WatchEvent.LIST_ERROR, message);
|
|
303
|
+
expect(Log.debug).toHaveBeenCalledWith(`Watch event ${WatchEvent.LIST_ERROR} received. ${message}.`);
|
|
304
|
+
});
|
|
305
|
+
it("should handle LIST events", () => {
|
|
306
|
+
const podList = {
|
|
307
|
+
kind: "PodList",
|
|
308
|
+
apiVersion: "v1",
|
|
309
|
+
metadata: { resourceVersion: "10245" },
|
|
310
|
+
items: [],
|
|
311
|
+
};
|
|
312
|
+
const message = JSON.stringify(podList, undefined, 2);
|
|
313
|
+
logEvent(WatchEvent.LIST, message);
|
|
314
|
+
expect(Log.debug).toHaveBeenCalledWith(`Watch event ${WatchEvent.LIST} received. ${message}.`);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("should handle DATA_ERROR events", () => {
|
|
318
|
+
const message = "Test message";
|
|
319
|
+
logEvent(WatchEvent.DATA_ERROR, message);
|
|
320
|
+
expect(Log.debug).toHaveBeenCalledWith(`Watch event ${WatchEvent.DATA_ERROR} received. ${message}.`);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
@@ -7,14 +7,18 @@ import { filterNoMatchReason } from "./helpers";
|
|
|
7
7
|
import Log from "./logger";
|
|
8
8
|
import { Queue } from "./queue";
|
|
9
9
|
import { Binding, Event } from "./types";
|
|
10
|
+
import { metricsCollector } from "./metrics";
|
|
10
11
|
|
|
11
12
|
// Watch configuration
|
|
12
13
|
const watchCfg: WatchCfg = {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
? parseInt(process.env.
|
|
14
|
+
resyncFailureMax: process.env.PEPR_RESYNC_FAILURE_MAX ? parseInt(process.env.PEPR_RESYNC_FAILURE_MAX, 10) : 5,
|
|
15
|
+
resyncDelaySec: process.env.PEPR_RESYNC_DELAY_SECONDS ? parseInt(process.env.PEPR_RESYNC_DELAY_SECONDS, 10) : 5,
|
|
16
|
+
lastSeenLimitSeconds: process.env.PEPR_LAST_SEEN_LIMIT_SECONDS
|
|
17
|
+
? parseInt(process.env.PEPR_LAST_SEEN_LIMIT_SECONDS, 10)
|
|
17
18
|
: 300,
|
|
19
|
+
relistIntervalSec: process.env.PEPR_RELIST_INTERVAL_SECONDS
|
|
20
|
+
? parseInt(process.env.PEPR_RELIST_INTERVAL_SECONDS, 10)
|
|
21
|
+
: 1800,
|
|
18
22
|
};
|
|
19
23
|
|
|
20
24
|
// Map the event to the watch phase
|
|
@@ -105,6 +109,18 @@ async function runBinding(binding: Binding, capabilityNamespaces: string[]) {
|
|
|
105
109
|
watcher.events.on(WatchEvent.NETWORK_ERROR, err => logEvent(WatchEvent.NETWORK_ERROR, err.message));
|
|
106
110
|
watcher.events.on(WatchEvent.LIST_ERROR, err => logEvent(WatchEvent.LIST_ERROR, err.message));
|
|
107
111
|
watcher.events.on(WatchEvent.LIST, list => logEvent(WatchEvent.LIST, JSON.stringify(list, undefined, 2)));
|
|
112
|
+
watcher.events.on(WatchEvent.CACHE_MISS, windowName => {
|
|
113
|
+
metricsCollector.incCacheMiss(windowName);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
watcher.events.on(WatchEvent.INIT_CACHE_MISS, windowName => {
|
|
117
|
+
metricsCollector.initCacheMissWindow(windowName);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
watcher.events.on(WatchEvent.INC_RESYNC_FAILURE_COUNT, retryCount => {
|
|
121
|
+
metricsCollector.incRetryCount(retryCount);
|
|
122
|
+
});
|
|
123
|
+
|
|
108
124
|
// Start the watch
|
|
109
125
|
try {
|
|
110
126
|
await watcher.start();
|