Skip to content

Commit 7960e46

Browse files
authored
Merge pull request #265 from snyk/feat/workload_info_cache
feat: workload info caching
2 parents be78a00 + 3286202 commit 7960e46

File tree

6 files changed

+217
-3
lines changed

6 files changed

+217
-3
lines changed

config.default.json

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
"MAX_SIZE": 10000,
88
"MAX_AGE_MS": 86400000
99
},
10+
"WORKLOADS_SCANNED_CACHE": {
11+
"MAX_SIZE": 10000,
12+
"MAX_AGE_MS": 60000
13+
},
1014
"WORKLOADS_TO_SCAN_QUEUE_WORKER_COUNT": 10,
1115
"METADATA_TO_SEND_QUEUE_WORKER_COUNT": 10,
1216
"INTEGRATION_ID": "203210a3-1de0-4ed3-b6a6-3acd24b71639",

package-lock.json

+131
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"@types/lru-cache": "^5.1.0",
3232
"@types/needle": "^2.0.4",
3333
"@types/node": "^10.12.5",
34+
"@types/sinon": "^7.5.1",
3435
"async": "^2.6.2",
3536
"bunyan": "^1.8.12",
3637
"child-process-promise": "^2.2.1",
@@ -49,6 +50,7 @@
4950
"@typescript-eslint/parser": "^2.6.1",
5051
"eslint": "^6.6.0",
5152
"eslint-config-prettier": "^6.5.0",
53+
"sinon": "^8.0.1",
5254
"tap": "github:snyk/node-tap#alternative-runtimes",
5355
"ts-node": "^8.1.0",
5456
"tsc-watch": "^1.0.30"

src/kube-scanner/watchers/handlers/pod.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,14 @@ export async function podWatchHandler(pod: V1Pod): Promise<void> {
9898
// every element contains the workload information, so we can get it from the first one
9999
const workloadMember = workloadMetadata[0];
100100
const workloadMetadataPayload = constructHomebaseWorkloadMetadataPayload(workloadMember);
101-
metadataToSendQueue.push({workloadMetadataPayload});
101+
const workloadLocator = workloadMetadataPayload.workloadLocator;
102+
const workloadKey = `${workloadLocator.namespace}/${workloadLocator.type}/${workloadLocator.name}`;
103+
const workloadRevision = workloadMember.revision ? workloadMember.revision.toString() : ''; // this is actually the observed generation
104+
if (state.workloadsAlreadyScanned.get(workloadKey) !== workloadRevision) { // either not exists or different
105+
state.workloadsAlreadyScanned.set(workloadKey, workloadRevision); // empty string takes zero bytes and is !== undefined
106+
metadataToSendQueue.push({workloadMetadataPayload});
107+
}
108+
102109
const workloadName = workloadMember.name;
103110
const workloadWorker = new WorkloadWorker(workloadName);
104111
handleReadyPod(workloadWorker, workloadMetadata);

src/state.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import * as lruCache from 'lru-cache';
33
import config = require('./common/config');
44

5-
const lruCacheOptions = {
5+
const imagesLruCacheOptions = {
66
// limit cache size so we don't exceed memory limit
77
max: config.IMAGES_SCANNED_CACHE.MAX_SIZE,
88
// limit cache life so if our backend loses track of an image's data,
@@ -11,8 +11,18 @@ const lruCacheOptions = {
1111
updateAgeOnGet: false,
1212
};
1313

14+
const workloadsLruCacheOptions = {
15+
// limit cache size so we don't exceed memory limit
16+
max: config.WORKLOADS_SCANNED_CACHE.MAX_SIZE,
17+
// limit cache life so if our backend loses track of an image's data,
18+
// eventually we will report again for that image, if it's still relevant
19+
maxAge: config.WORKLOADS_SCANNED_CACHE.MAX_AGE_MS,
20+
updateAgeOnGet: false,
21+
};
22+
1423
const state = {
15-
imagesAlreadyScanned: new lruCache<string, string>(lruCacheOptions),
24+
imagesAlreadyScanned: new lruCache<string, string>(imagesLruCacheOptions),
25+
workloadsAlreadyScanned: new lruCache<string, string>(workloadsLruCacheOptions),
1626
};
1727

1828
export = state;
+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import * as tap from 'tap';
2+
import sinon = require('sinon');
3+
import * as sleep from 'sleep-promise';
4+
import * as fs from 'fs';
5+
import * as YAML from 'yaml';
6+
import async = require('async');
7+
8+
import { V1PodSpec, V1Pod } from '@kubernetes/client-node';
9+
import transmitterTypes = require('../../src/transmitter/types');
10+
import * as metadataExtractor from '../../src/kube-scanner/metadata-extractor';
11+
12+
let pushCallCount = 0;
13+
sinon.stub(async, 'queue').returns({ error: () => { }, push: () => pushCallCount++ } as any);
14+
15+
import * as pod from '../../src/kube-scanner/watchers/handlers/pod';
16+
17+
tap.test('image and workload image cache', async (t) => {
18+
const podSpecFixture = fs.readFileSync('./test/fixtures/pod-spec.json', 'utf8');
19+
const podSpec: V1PodSpec = YAML.parse(podSpecFixture);
20+
const workloadMetadata: transmitterTypes.IWorkload[] = [
21+
{
22+
type: 'type',
23+
name: 'workloadName',
24+
namespace: 'spacename',
25+
labels: undefined,
26+
annotations: undefined,
27+
uid: 'udi',
28+
specLabels: undefined,
29+
specAnnotations: undefined,
30+
containerName: 'contener',
31+
imageName: 'myImage:tag',
32+
imageId: 'does this matter?',
33+
cluster: 'grapefruit',
34+
revision: 1,
35+
podSpec,
36+
},
37+
];
38+
39+
sinon.stub(metadataExtractor, 'buildMetadataForWorkload').resolves(workloadMetadata);
40+
41+
t.teardown(() => {
42+
(async['queue'] as any).restore();
43+
(metadataExtractor['buildMetadataForWorkload'] as any).restore();
44+
});
45+
46+
const podFixture = fs.readFileSync('./test/fixtures/sidecar-containers/pod.yaml', 'utf8');
47+
const podObject: V1Pod = YAML.parse(podFixture);
48+
await pod.podWatchHandler(podObject);
49+
await sleep(500);
50+
t.equals(pushCallCount, 2, 'pushed data to send');
51+
52+
await pod.podWatchHandler(podObject);
53+
await sleep(500);
54+
t.equals(pushCallCount, 2, 'cached info, no data pushed to send');
55+
56+
workloadMetadata[0].imageId = 'newImageName';
57+
await pod.podWatchHandler(podObject);
58+
await sleep(1000);
59+
t.equals(pushCallCount, 3, 'new image parsed, workload is cached');
60+
});

0 commit comments

Comments
 (0)