Skip to content

Commit

Permalink
refactor: collapse examples / async-ify callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
btlghrants committed Aug 23, 2024
1 parent f578b82 commit cfcab46
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 116 deletions.
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,40 @@ Here, you'll find practical implementations that demonstrate the use of Pepr in

Most examples are designed around a test suite that demonstrates / verifies how they work.

To run all example suites in a single command:
To run all example suites:

```sh
npm run test:e2e
```

To run only a single example suite instead, specify which using `-w` flag:
To run all example suites against a custom version of Pepr, specify which using the `--image` flag:

```sh
npm run test:e2e -- --image pepr:dev
```

To run a single example suite, specify which to run using `-w` flag:

```sh
# npm run test:e2e -w <npm module name>

npm run test:e2e -w hello-pepr-validate -- --image pepr:dev
npm run test:e2e -w hello-pepr-validate
```

To get even more targeted, you can select spec tests to run using some `-- --passthru` magic:
To run a subset of tests, give Jest the specifics via the `--passthru` flag:

```sh
# npm run test:e2e -w <npm module name> -- --passthru="<jest flags>"

npm run test:e2e -w hello-pepr-validate -- --passthru="--testNamePattern='validate creates'"
```

## Using k9s
## Troubleshooting

### Viewing the Example Cluster (via K9s)

```sh
# KUBECONFIG=$(k3d kubeconfig write pexex-<npm module name>-e2e) k9s

KUBECONFIG=$(k3d kubeconfig write pexex-hello-pepr-reconcile-e2e) k9s
```
28 changes: 0 additions & 28 deletions hello-pepr-reconcile/capabilities/reconcile.config.yaml

This file was deleted.

74 changes: 37 additions & 37 deletions hello-pepr-reconcile/capabilities/reconcile.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { beforeAll, afterAll, describe, it, jest, expect } from "@jest/globals";
import { TestRunCfg } from "helpers/src/TestRunCfg";
import { mins, secs, timed } from "helpers/src/time";
import { kind } from "kubernetes-fluent-client";
import { fullCreate } from "helpers/src/general";
import { moduleUp, moduleDown, untilLogged, logs } from "helpers/src/pepr";
import { clean } from "helpers/src/cluster"
import { execSync } from "child_process";
import { clean } from "helpers/src/cluster";
import { K8s, kind } from "pepr";

const trc = new TestRunCfg(__filename);

Expand All @@ -19,47 +18,48 @@ describe("reconcile.ts", () => {
describe("tests reconcile module", () => {
let logz: string[]

beforeAll(async () => {
const file = `${trc.root()}/capabilities/reconcile.config.yaml`;
beforeAll(async () => {
const file = `${trc.root()}/capabilities/scenario.resources.yaml`;
await timed(`load: ${file}`, async () => {
const resources = await trc.load(file);
await fullCreate(resources, kind);
execSync(`sh ${trc.root()}/capabilities/script.sh`);
await untilLogged("Callback: Reconciling cm-three");
let [ ns, slow, fast ] = await trc.load(file)

await fullCreate([ns, slow, fast]) // slow = A, fast = X

await K8s(kind[slow.kind]).Apply({...slow, data: { note: "B"}})
await K8s(kind[slow.kind]).Apply({...slow, data: { note: "C"}})

await K8s(kind[fast.kind]).Apply({...fast, data: { note: "Y"}})
await K8s(kind[fast.kind]).Apply({...fast, data: { note: "Z"}})

await untilLogged("Callback: Reconciling cm-slow C-")
await untilLogged("Callback: Reconciling cm-fast Z-")
logz = await logs();
});
}, mins(2));

it("maintains callback order even when execution times vary", () => {
it("maintains callback order within a queue, paralellizes across queues", () => {
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// cm-slow |+ A -|+ B -|+ C - |
// cm-fast |+ X -|+ Y -|+ Z -|
const results = logz.filter(l => l.includes("Callback: Reconciling"))
expect(results[0]).toContain("cm-one")
expect(results[1]).toContain("cm-two")
expect(results[2]).toContain("cm-three")
}, secs(10));

it("assert that each Queue still runs in order and that they run independent", () => {
const allResults = logz.filter(l => l.includes("Pod with name"))
const aStack: string[] = ["Pod with name a has color red", "Pod with name a has color green", "Pod with name a has color blue","Pod with name a has color yellow"]
const bStack: string[] = ["Pod with name b has color red", "Pod with name b has color green", "Pod with name b has color blue"]

/*
* Background - Tests that each stack run independently.
* all aStack results should be before bStack results
* even though they were created at the same time.
*
* aStack and bStack independently should run in the order they were created in the queue.
*/

// Combine results in the order in quick they should be processed
const abStack = [...aStack, ...bStack]
allResults.forEach((result) => {
if(result.includes(abStack[0])) {
abStack.shift()
}
let wants = [
"cm-slow A+",
"cm-fast X+",
"cm-fast X-",
"cm-fast Y+",
"cm-slow A-",
"cm-slow B+",
"cm-fast Y-",
"cm-fast Z+",
"cm-fast Z-",
"cm-slow B-",
"cm-slow C+",
"cm-slow C-"
]
wants.forEach((wanted, atIndex) => {
expect(results[atIndex]).toContain(wanted)
})
expect(abStack.length).toEqual(0)


}, mins(3));
}, secs(10));
});
});
57 changes: 24 additions & 33 deletions hello-pepr-reconcile/capabilities/reconcile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,36 @@ import { Capability, Log, a } from "pepr";
export const HelloPeprReconcile = new Capability({
name: "hello-pepr-reconcile",
description: "A Kubernetes Operator that manages WebApps",
namespaces: ["pepr-demo"],
namespaces: ["hello-pepr-reconcile"],
});

const { When } = HelloPeprReconcile;

let i = 0;
const log = (name, note, tag) => {
Log.info(`Callback: Reconciling ${name} ${note}${tag}`);
}

const task = (cm, durationMs) => new Promise<void>(resolve => {
const name = cm.metadata.name
const note = cm.data.note
log(name, note, "+")
setTimeout(() => { log(name, note, "-") ; resolve() }, durationMs)
});
const fast = (cm) => task(cm, 300)
const slow = (cm) => task(cm, 500)
const oops = (cm) => { throw `oops: ${cm}` }

When(a.ConfigMap)
.IsCreatedOrUpdated()
.InNamespace("pepr-demo")
.Reconcile(async instance => {
if (instance.metadata?.name !== "kube-root-ca.crt") {
return new Promise(resolve => {
const timeOut = i++ % 2 == 0 ? 20000 : 5000;
setTimeout(() => {
Log.info(
`Callback: Reconciling ${instance.metadata.name} after ${
timeOut / 1000
}s`,
);
resolve();
}, timeOut);
});
}
});
.InNamespace("hello-pepr-reconcile")
.Reconcile(function keepsOrder(cm) {
const { name } = cm.metadata;

const logPod = (name: string, color: string) => {
Log.info(`Pod with name ${name} has color ${color}.`);
};
When(a.Pod)
.IsCreatedOrUpdated()
.Reconcile(po => {
const { labels, name } = po.metadata;
if (name === "kube-root-ca.crt") { return }

switch (name) {
case "a":
setTimeout(() => logPod(name, labels.color), 1000);
break;
case "b":
setTimeout(() => logPod(name, labels.color), 10000);
break;
}
return (
name === "cm-slow" ? slow(cm) :
name === "cm-fast" ? fast(cm) :
oops(cm)
)
});
20 changes: 20 additions & 0 deletions hello-pepr-reconcile/capabilities/scenario.resources.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: v1
kind: Namespace
metadata:
name: hello-pepr-reconcile
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-slow
namespace: hello-pepr-reconcile
data:
note: A
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-fast
namespace: hello-pepr-reconcile
data:
note: X
13 changes: 0 additions & 13 deletions hello-pepr-reconcile/capabilities/script.sh

This file was deleted.

0 comments on commit cfcab46

Please sign in to comment.