Skip to content

Commit

Permalink
feat: update pepr reconcile strategy (#1127)
Browse files Browse the repository at this point in the history
## Description

Adds some new options for the PEPR_RECONCILE_STRATEGY configuration
option.

End to End Test - Pepr Excellent Examples
-
[hello-pepr-reconcile-kind](https://github.com/defenseunicorns/pepr-excellent-examples/hello-pepr-reconcile-kind)
-
[hello-pepr-reconcile-kindns](https://github.com/defenseunicorns/pepr-excellent-examples/hello-pepr-reconcile-kindns)
-
[hello-pepr-reconcile-kindnsname](https://github.com/defenseunicorns/pepr-excellent-examples/hello-pepr-reconcile-kindnsname)
-
[hello-pepr-reconcile-global](https://github.com/defenseunicorns/pepr-excellent-examples/hello-pepr-reconcile-global)

## Related Issue
Resolves #1126 

## Type of change

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Other (security config, docs update, etc)

## Checklist before merging
- [x] Unit,
[Journey](https://github.com/defenseunicorns/pepr/tree/main/journey),
[E2E Tests](https://github.com/defenseunicorns/pepr-excellent-examples),
[docs](https://github.com/defenseunicorns/pepr/tree/main/docs),
[adr](https://github.com/defenseunicorns/pepr/tree/main/adr) added or
updated as needed
- [ ] [Contributor Guide
Steps](https://docs.pepr.dev/main/contribute/#submitting-a-pull-request)
followed

---------

Co-authored-by: Case Wylie <[email protected]>
  • Loading branch information
btlghrants and cmwylie19 authored Sep 16, 2024
1 parent c36b892 commit c9c152b
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 135 deletions.
12 changes: 6 additions & 6 deletions docs/030_user-guide/120_customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,18 @@ The Watch configuration is a part of the Pepr module that allows you to watch fo

## Configuring Reconcile

The [Reconcile Action](./030_actions/030_reconcile.md) allows you to maintain ordering of resource updates processed by a Pepr controller. The Reconcile configuration can be customized by specific enviroment variables of the Watcher Deployment and can be set in the `package.json` or in the helm `values.yaml` file.
The [Reconcile Action](./030_actions/030_reconcile.md) allows you to maintain ordering of resource updates processed by a Pepr controller. The Reconcile configuration can be customized via enviroment variable on the Watcher Deployment, which can be set in the `package.json` or in the helm `values.yaml` file.

| Field | Description | Example Values |
|-|-|-|
| `PEPR_RECONCILE_STRATEGY` | How Pepr should order resource updates being Reconcile()'d. | default: `"singular"` |
| `PEPR_RECONCILE_STRATEGY` | How Pepr should order resource updates being Reconcile()'d. | default: `"kind"` |

| Available Options ||
|-|-|
| `singular` | Pepr will keep a single queue of events across _all_ Reconcile()'d resources of a kind.
| `sharded` | Pepr will keep multiple queues of events, one for each resource _instance_ of a kind.


| `kind` | separate queues of events for Reconcile()'d resources of a kind |
| `kindNs` | separate queues of events for Reconcile()'d resources of a kind, within a namespace |
| `kindNsName` | separate queues of events for Reconcile()'d resources of a kind, within a namespace, per name |
| `global` | a single queue of events for all Reconcile()'d resources |

## Customizing with Helm

Expand Down
176 changes: 50 additions & 126 deletions src/lib/watch-processor.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
import { afterAll, beforeEach, beforeAll, describe, expect, it, jest } from "@jest/globals";
import { afterAll, beforeEach, describe, expect, it, jest } from "@jest/globals";
import { GenericClass, K8s, KubernetesObject, kind } from "kubernetes-fluent-client";
import { K8sInit, WatchPhase } from "kubernetes-fluent-client/dist/fluent/types";
import { WatchCfg, WatchEvent, Watcher } from "kubernetes-fluent-client/dist/fluent/watch";
Expand Down Expand Up @@ -330,133 +330,57 @@ describe("logEvent function", () => {
});

describe("queueKey", () => {
describe("PEPR_RECONCILE_STRATEGY=sharded", () => {
const original = process.env.PEPR_RECONCILE_STRATEGY;

beforeAll(() => {
process.env.PEPR_RECONCILE_STRATEGY = "sharded";
});
afterAll(() => {
process.env.PEPR_RECONCILE_STRATEGY = original;
});

it("should return correct key for an object with 'kind/name/namespace'", () => {
const obj: KubernetesObject = {
kind: "Pod",
metadata: {
name: "my-pod",
namespace: "my-namespace",
},
};

expect(queueKey(obj)).toBe("Pod/my-pod/my-namespace");
});

it("should handle objects with missing namespace", () => {
const obj: KubernetesObject = {
kind: "Pod",
metadata: {
name: "my-pod",
},
};

expect(queueKey(obj)).toBe("Pod/my-pod/cluster-scoped");
});

it("should handle objects with missing name", () => {
const obj: KubernetesObject = {
kind: "Pod",
metadata: {
namespace: "my-namespace",
},
};

expect(queueKey(obj)).toBe("Pod/Unnamed/my-namespace");
});

it("should handle objects with missing metadata", () => {
const obj: KubernetesObject = {
kind: "Pod",
};

expect(queueKey(obj)).toBe("Pod/Unnamed/cluster-scoped");
});

it("should handle objects with missing kind", () => {
const obj: KubernetesObject = {
metadata: {
name: "my-pod",
namespace: "my-namespace",
},
};

expect(queueKey(obj)).toBe("UnknownKind/my-pod/my-namespace");
});

it("should handle completely empty objects", () => {
const obj: KubernetesObject = {};

expect(queueKey(obj)).toBe("UnknownKind/Unnamed/cluster-scoped");
});
const withKindNsName = { kind: "Pod", metadata: { namespace: "my-ns", name: "my-name" } } as KubernetesObject;
const withKindNs = { kind: "Pod", metadata: { namespace: "my-ns" } } as KubernetesObject;
const withKindName = { kind: "Pod", metadata: { name: "my-name" } } as KubernetesObject;
const withNsName = { metadata: { namespace: "my-ns", name: "my-name" } } as KubernetesObject;
const withKind = { kind: "Pod" } as KubernetesObject;
const withNs = { metadata: { namespace: "my-ns" } } as KubernetesObject;
const withName = { metadata: { name: "my-name" } } as KubernetesObject;
const withNone = {} as KubernetesObject;

const original = process.env.PEPR_RECONCILE_STRATEGY;

it.each([
["kind", withKindNsName, "Pod"],
["kind", withKindNs, "Pod"],
["kind", withKindName, "Pod"],
["kind", withNsName, "UnknownKind"],
["kind", withKind, "Pod"],
["kind", withNs, "UnknownKind"],
["kind", withName, "UnknownKind"],
["kind", withNone, "UnknownKind"],
["kindNs", withKindNsName, "Pod/my-ns"],
["kindNs", withKindNs, "Pod/my-ns"],
["kindNs", withKindName, "Pod/cluster-scoped"],
["kindNs", withNsName, "UnknownKind/my-ns"],
["kindNs", withKind, "Pod/cluster-scoped"],
["kindNs", withNs, "UnknownKind/my-ns"],
["kindNs", withName, "UnknownKind/cluster-scoped"],
["kindNs", withNone, "UnknownKind/cluster-scoped"],
["kindNsName", withKindNsName, "Pod/my-ns/my-name"],
["kindNsName", withKindNs, "Pod/my-ns/Unnamed"],
["kindNsName", withKindName, "Pod/cluster-scoped/my-name"],
["kindNsName", withNsName, "UnknownKind/my-ns/my-name"],
["kindNsName", withKind, "Pod/cluster-scoped/Unnamed"],
["kindNsName", withNs, "UnknownKind/my-ns/Unnamed"],
["kindNsName", withName, "UnknownKind/cluster-scoped/my-name"],
["kindNsName", withNone, "UnknownKind/cluster-scoped/Unnamed"],
["global", withKindNsName, "global"],
["global", withKindNs, "global"],
["global", withKindName, "global"],
["global", withNsName, "global"],
["global", withKind, "global"],
["global", withNs, "global"],
["global", withName, "global"],
["global", withNone, "global"],
])("PEPR_RECONCILE_STRATEGY='%s' over '%j' becomes '%s'", (strat, obj, key) => {
process.env.PEPR_RECONCILE_STRATEGY = strat;
expect(queueKey(obj)).toBe(key);
});

describe("PEPR_RECONCILE_STRATEGY=singular", () => {
const original = process.env.PEPR_RECONCILE_STRATEGY;

beforeAll(() => {
process.env.PEPR_RECONCILE_STRATEGY = "singular";
});
afterAll(() => {
process.env.PEPR_RECONCILE_STRATEGY = original;
});

it("should return correct key for an object with 'kind/namespace'", () => {
const obj: KubernetesObject = {
kind: "Pod",
metadata: {
name: "my-pod",
namespace: "my-namespace",
},
};

expect(queueKey(obj)).toBe("Pod/my-namespace");
});

it("should handle objects with missing namespace", () => {
const obj: KubernetesObject = {
kind: "Pod",
metadata: {
name: "my-pod",
},
};

expect(queueKey(obj)).toBe("Pod/cluster-scoped");
});

it("should handle objects with missing metadata", () => {
const obj: KubernetesObject = {
kind: "Pod",
};

expect(queueKey(obj)).toBe("Pod/cluster-scoped");
});

it("should handle objects with missing kind", () => {
const obj: KubernetesObject = {
metadata: {
name: "my-pod",
namespace: "my-namespace",
},
};

expect(queueKey(obj)).toBe("UnknownKind/my-namespace");
});

it("should handle completely empty objects", () => {
const obj: KubernetesObject = {};

expect(queueKey(obj)).toBe("UnknownKind/cluster-scoped");
});
afterAll(() => {
process.env.PEPR_RECONCILE_STRATEGY = original;
});
});

Expand Down
12 changes: 9 additions & 3 deletions src/lib/watch-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ const queues: Record<string, Queue<KubernetesObject>> = {};
* @returns The key to a Queue in the list of queues
*/
export function queueKey(obj: KubernetesObject) {
const options = ["singular", "sharded"];
const d3fault = "singular";
const options = ["kind", "kindNs", "kindNsName", "global"];
const d3fault = "kind";

let strat = process.env.PEPR_RECONCILE_STRATEGY || d3fault;
strat = options.includes(strat) ? strat : d3fault;
Expand All @@ -29,7 +29,13 @@ export function queueKey(obj: KubernetesObject) {
const kind = obj.kind ?? "UnknownKind";
const name = obj.metadata?.name ?? "Unnamed";

return strat === "singular" ? `${kind}/${ns}` : `${kind}/${name}/${ns}`;
const lookup: Record<string, string> = {
kind: `${kind}`,
kindNs: `${kind}/${ns}`,
kindNsName: `${kind}/${ns}/${name}`,
global: "global",
};
return lookup[strat];
}

export function getOrCreateQueue(obj: KubernetesObject) {
Expand Down

0 comments on commit c9c152b

Please sign in to comment.