Skip to content

Commit 75a5440

Browse files
authored
Merge pull request #1044 from snyk/test/caching-queueing
[RUN-2156] test: image and workload caching and queueing
2 parents fa28338 + 2450ddb commit 75a5440

File tree

3 files changed

+240
-2
lines changed

3 files changed

+240
-2
lines changed

src/scanner/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ export function getUniqueImages(workloadMetadata: IWorkload[]): IScanImage[] {
113113
return Object.values(uniqueImages);
114114
}
115115

116-
async function scanImagesAndSendResults(
116+
/** Exported for testing */
117+
export async function scanImagesAndSendResults(
117118
workloadName: string,
118119
pulledImages: IPullableImage[],
119120
workloadMetadata: IWorkload[],

src/supervisor/watchers/handlers/pod.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ import { paginatedClusterList, paginatedNamespacedList } from './pagination';
2424
import { trimWorkload } from '../../workload-sanitization';
2525
import { deleteWorkloadFromScanQueue, workloadsToScanQueue } from './queue';
2626

27-
async function handleReadyPod(workloadMetadata: IWorkload[]): Promise<void> {
27+
/** Exported for testing */
28+
export async function handleReadyPod(
29+
workloadMetadata: IWorkload[],
30+
): Promise<void> {
2831
const workloadToScan: IWorkload[] = [];
2932
for (const workload of workloadMetadata) {
3033
const scanned = await getWorkloadImageAlreadyScanned(
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
import * as state from '../../../src/state';
2+
import * as transmitter from '../../../src/transmitter';
3+
import * as scannerImages from '../../../src/scanner/images';
4+
import * as transmitterPayload from '../../../src/transmitter/payload';
5+
6+
import { scanImagesAndSendResults } from '../../../src/scanner';
7+
import { handleReadyPod } from '../../../src/supervisor/watchers/handlers/pod';
8+
import { workloadsToScanQueue } from '../../../src/supervisor/watchers/handlers/queue';
9+
10+
import type { IWorkload } from '../../../src/transmitter/types';
11+
import type { LegacyPluginResponse } from '../../../src/scanner/images/docker-plugin-shim';
12+
13+
describe('scan results caching', () => {
14+
const workload: IWorkload = {
15+
cluster: 'cluster',
16+
namespace: 'namespace',
17+
type: 'type',
18+
uid: 'uid',
19+
name: 'name',
20+
imageName: 'imageName',
21+
imageId: 'imageId',
22+
containerName: 'containerName',
23+
revision: 1,
24+
podSpec: { containers: [] },
25+
annotations: undefined,
26+
labels: undefined,
27+
specAnnotations: undefined,
28+
specLabels: undefined,
29+
};
30+
31+
describe('when receiving workloads to scan', () => {
32+
afterEach(() => {
33+
jest.restoreAllMocks();
34+
});
35+
36+
it('stores workload images to cache and pushes to queue when not already seen', async () => {
37+
// Arrange
38+
const queuePushMock = jest
39+
.spyOn(workloadsToScanQueue, 'push')
40+
.mockReturnValue();
41+
const setWorkloadImageAlreadyScannedMock = jest
42+
.spyOn(state, 'setWorkloadImageAlreadyScanned')
43+
.mockResolvedValue(true);
44+
45+
// Act
46+
const workloadMetadata: IWorkload[] = [workload];
47+
await handleReadyPod(workloadMetadata);
48+
49+
// Assert
50+
expect(queuePushMock).toHaveBeenCalledWith({
51+
key: workload.uid,
52+
workloadMetadata,
53+
enqueueTimestampMs: expect.any(Number),
54+
});
55+
expect(setWorkloadImageAlreadyScannedMock).toHaveBeenCalledWith(
56+
workload,
57+
'imageName',
58+
'imageId',
59+
);
60+
61+
setWorkloadImageAlreadyScannedMock.mockRestore();
62+
queuePushMock.mockRestore();
63+
});
64+
65+
it('stores images to cache and pushes to queue when imageId is different', async () => {
66+
// Arrange
67+
const queuePushMock = jest
68+
.spyOn(workloadsToScanQueue, 'push')
69+
.mockReturnValue();
70+
const setWorkloadImageAlreadyScannedMock = jest
71+
.spyOn(state, 'setWorkloadImageAlreadyScanned')
72+
.mockResolvedValue(true);
73+
const workloadWithNewImageId: IWorkload = {
74+
...workload,
75+
imageId: 'newImageId',
76+
};
77+
78+
// Act
79+
const workloadMetadata: IWorkload[] = [workloadWithNewImageId];
80+
await handleReadyPod(workloadMetadata);
81+
82+
// Assert
83+
expect(queuePushMock).toHaveBeenCalledWith({
84+
key: workload.uid,
85+
workloadMetadata,
86+
enqueueTimestampMs: expect.any(Number),
87+
});
88+
expect(setWorkloadImageAlreadyScannedMock).toHaveBeenCalledWith(
89+
workloadWithNewImageId,
90+
'imageName',
91+
'newImageId',
92+
);
93+
94+
setWorkloadImageAlreadyScannedMock.mockRestore();
95+
queuePushMock.mockRestore();
96+
});
97+
98+
it('skips storing images to cache and skips pushing to queue when imageId is already seen', async () => {
99+
// Arrange
100+
await state.setWorkloadImageAlreadyScanned(
101+
workload,
102+
workload.imageName,
103+
workload.imageId,
104+
);
105+
const queuePushMock = jest
106+
.spyOn(workloadsToScanQueue, 'push')
107+
.mockReturnValue();
108+
const setWorkloadImageAlreadyScannedMock = jest
109+
.spyOn(state, 'setWorkloadImageAlreadyScanned')
110+
.mockResolvedValue(true);
111+
112+
// Act
113+
const workloadMetadata: IWorkload[] = [workload];
114+
await handleReadyPod(workloadMetadata);
115+
116+
// Assert
117+
expect(queuePushMock).not.toHaveBeenCalled();
118+
expect(setWorkloadImageAlreadyScannedMock).not.toHaveBeenCalled();
119+
120+
setWorkloadImageAlreadyScannedMock.mockRestore();
121+
queuePushMock.mockRestore();
122+
});
123+
});
124+
125+
describe('when scanning and sending scan results', () => {
126+
afterEach(() => {
127+
jest.restoreAllMocks();
128+
});
129+
130+
test.each([
131+
[
132+
'with cached workload state',
133+
`${workload.namespace}/${workload.type}/${workload.uid}`,
134+
undefined,
135+
],
136+
[
137+
'with cached image state',
138+
undefined,
139+
`${workload.namespace}/${workload.type}/${workload.uid}/${workload.imageId}`,
140+
],
141+
[
142+
'with cached workload and image state',
143+
`${workload.namespace}/${workload.type}/${workload.uid}`,
144+
`${workload.namespace}/${workload.type}/${workload.uid}/${workload.imageId}`,
145+
],
146+
])('%s', async (_testCaseName, workloadState, imageState) => {
147+
// Arrange
148+
const scanImagesMock = jest
149+
.spyOn(scannerImages, 'scanImages')
150+
.mockResolvedValue([
151+
{
152+
image: 'image',
153+
imageWithDigest:
154+
'image@sha256:3e46ed577bf26f1bd0bf265b25b3ac3f72831bc87edee0c9da7bb8006b9b8836',
155+
imageWithTag: 'image:tag',
156+
pluginResult: {} as LegacyPluginResponse,
157+
scanResults: [],
158+
},
159+
]);
160+
const constructScanResultsMock = jest
161+
.spyOn(transmitterPayload, 'constructScanResults')
162+
.mockReturnValue([]);
163+
const sendScanResultsMock = jest.spyOn(transmitter, 'sendScanResults');
164+
165+
// Act
166+
await state.setWorkloadAlreadyScanned(workload, workloadState as any);
167+
await state.setWorkloadImageAlreadyScanned(
168+
workload,
169+
workload.imageName,
170+
imageState as any,
171+
);
172+
173+
const workloadName = 'mock';
174+
const pulledImages = [];
175+
const workloadMetadata: IWorkload[] = [workload];
176+
const telemetry = {};
177+
await scanImagesAndSendResults(
178+
workloadName,
179+
pulledImages,
180+
workloadMetadata,
181+
telemetry,
182+
);
183+
184+
// Assert
185+
expect(sendScanResultsMock).toHaveBeenCalled();
186+
187+
sendScanResultsMock.mockRestore();
188+
constructScanResultsMock.mockRestore();
189+
scanImagesMock.mockRestore();
190+
});
191+
192+
it('skips sending scan results when a workload is no longer in cache', async () => {
193+
// Arrange
194+
const scanImagesMock = jest
195+
.spyOn(scannerImages, 'scanImages')
196+
.mockResolvedValue([
197+
{
198+
image: 'image',
199+
imageWithDigest:
200+
'image@sha256:3e46ed577bf26f1bd0bf265b25b3ac3f72831bc87edee0c9da7bb8006b9b8836',
201+
imageWithTag: 'image:tag',
202+
pluginResult: {} as LegacyPluginResponse,
203+
scanResults: [],
204+
},
205+
]);
206+
const sendScanResultsMock = jest.spyOn(transmitter, 'sendScanResults');
207+
208+
// Act
209+
await state.setWorkloadAlreadyScanned(workload, undefined as any);
210+
await state.setWorkloadImageAlreadyScanned(
211+
workload,
212+
workload.imageName,
213+
undefined as any,
214+
);
215+
216+
const workloadName = 'mock';
217+
const pulledImages = [];
218+
const workloadMetadata: IWorkload[] = [workload];
219+
const telemetry = {};
220+
await scanImagesAndSendResults(
221+
workloadName,
222+
pulledImages,
223+
workloadMetadata,
224+
telemetry,
225+
);
226+
227+
// Assert
228+
expect(sendScanResultsMock).not.toHaveBeenCalled();
229+
230+
sendScanResultsMock.mockRestore();
231+
scanImagesMock.mockRestore();
232+
});
233+
});
234+
});

0 commit comments

Comments
 (0)