Skip to content

Commit

Permalink
Implement bump <n> CLI command
Browse files Browse the repository at this point in the history
When restoring a backup, you may need to "bump" the counter of namespaces
to avoid conflicts with ASNs registered after the backup was made.

This commit adds a new CLI command to bump the counter of a namespace.

You can specify the namespace to bump or bump all namespaces.

Use the `stats` command to get a sense of how much you need to bump.
  • Loading branch information
pklaschka committed Sep 8, 2024
1 parent 421e186 commit f63616a
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 8 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ $$ n \in [r_{min}, r]; n = r_{min} + t \mod (r - r_{min}) $$
- [x] REST API
- [x] Visual Web Interface
- [x] CLI
- [ ] Bump counter to avoid collissions after restoring backups (where ASNs
- [x] Bump counter to avoid collissions after restoring backups (where ASNs
could have been generated after the time of the backup)
- [x] Analyze time between ASNs, providing the possibility to specify a
duration and a maximum collision probability for the bump (e.g. 1 hour,
Expand Down
70 changes: 70 additions & 0 deletions lib/cli/bump.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import z from "@collinhacks/zod";
import {
allManagedNamespaces,
CONFIG,
generateASN,
isManagedNamespace,
} from "$common/mod.ts";

const bumpArgs = z.object({
namespace: z.number({ coerce: true }).optional(),
_: z.tuple([z.literal("bump"), z.number({ coerce: true }).min(1).int()]),
});

/**
* Prints statistics about the ASNs.
* @param args the arguments to the command
* @param args.count the number of ASNs to generate (default: 1)
*/
export async function runBump(args: unknown) {
const parsedParams = bumpArgs.parse(args);

if (parsedParams.namespace && !isManagedNamespace(parsedParams.namespace)) {
console.error(
`Namespace ${parsedParams.namespace} (${CONFIG.ASN_PREFIX}${parsedParams.namespace}XXX) is not managed by the system.`,
);
console.error("It therefore cannot be bumped.");
console.error(
"Managed namespace numbers are:",
allManagedNamespaces().join(", "),
);
Deno.exit(1);
}

const namespaces = parsedParams.namespace
? [parsedParams.namespace]
: allManagedNamespaces();

const bumpDelta = parsedParams._[1];
const bumpedAt = new Date().toISOString();

console.log(
"Bumping namespaces:",
namespaces.map((n) => `${CONFIG.ASN_PREFIX}${n}XXX`).join(", "),
);
console.log("---");
await Promise.all(
namespaces.map((namespace) =>
generateASN(
{
bumpedBy: bumpDelta,
bumpedAt,
},
namespace,
bumpDelta,
).then((asn) => {
console.log(
"Bumped",
`${CONFIG.ASN_PREFIX}${namespace}XXX. ` +
`Next registered ASN will be ${CONFIG.ASN_PREFIX}${namespace}${
(asn.counter + 1).toString().padStart(3, "0")
}.`,
);
})
),
);
console.log("---");
console.log("Done.");

await Promise.resolve();
}
28 changes: 24 additions & 4 deletions lib/common/all-managed-namespaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import { CONFIG } from "$common/mod.ts";
* @returns all managed namespaces
*/
export function allManagedNamespaces() {
const minGeneric = Number.parseInt(
"1" + "0".repeat(CONFIG.ASN_NAMESPACE_RANGE.toString().length - 1),
);
const maxGeneric = CONFIG.ASN_NAMESPACE_RANGE - 1;
const minGeneric = getMinimumGenericRangeNamespace();
const maxGeneric = getMaximumGenericRangeNamespace();

return [
...Array.from(
Expand All @@ -19,3 +17,25 @@ export function allManagedNamespaces() {
...CONFIG.ADDITIONAL_MANAGED_NAMESPACES.map((v) => v.namespace),
];
}

function getMaximumGenericRangeNamespace() {
return CONFIG.ASN_NAMESPACE_RANGE - 1;
}

function getMinimumGenericRangeNamespace() {
return Number.parseInt(
"1" + "0".repeat(CONFIG.ASN_NAMESPACE_RANGE.toString().length - 1),
);
}

export function isManagedNamespace(namespace: number) {
if (namespace < getMinimumGenericRangeNamespace()) {
return false;
}
if (namespace <= getMaximumGenericRangeNamespace()) {
return true;
}
return CONFIG.ADDITIONAL_MANAGED_NAMESPACES.some((v) =>
v.namespace === namespace
);
}
15 changes: 12 additions & 3 deletions lib/common/asn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,28 @@ function getRange() {
export async function generateASN(
metadata: Record<string, unknown> = {},
namespace?: number,
deltaCounter = 1,
): Promise<ASNData> {
if (deltaCounter < 1) {
throw new Error("Delta counter must be at least 1");
}

if (deltaCounter % 1 !== 0) {
throw new Error("Delta counter must be an integer");
}

metadata = { ...metadata, generatedAt: new Date().toISOString() };
namespace = namespace ?? getCurrentNamespace();
let counter = 0;

await performAtomicTransaction(async (db) => {
const counterRes = await db.get<number>(["namespace", namespace]);
counter = counterRes.value ?? 1;
counter = (counterRes.value ?? 0) + deltaCounter;
return db.atomic()
.check(counterRes)
.set(
["namespace", namespace],
counter + 1,
counter,
)
.set(
["metadata", namespace, counter],
Expand All @@ -105,7 +114,7 @@ export async function generateASN(
}`,
namespace,
prefix: CONFIG.ASN_PREFIX,
counter,
counter: counter,
metadata,
};

Expand Down
5 changes: 5 additions & 0 deletions main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { runServer } from "$cli/server.ts";
import { printHelp } from "$cli/help.ts";
import { runGenerate } from "$cli/generate.ts";
import { runStats } from "$cli/stats.ts";
import { runBump } from "$cli/bump.ts";

export * from "$common/mod.ts";
export * from "$http/mod.tsx";
Expand All @@ -63,6 +64,10 @@ if (import.meta.main) {
await runStats(args);
}

if (args._[0] === "bump") {
await runBump(args);
}

if (args._[0] === "server" || args._.length === 0) {
await runServer(args);
}
Expand Down

0 comments on commit f63616a

Please sign in to comment.