Add locks to allocations

This commit is contained in:
Owen
2026-05-06 15:58:51 -07:00
parent 65ee9b9544
commit b046ab7513

View File

@@ -6,6 +6,7 @@ import z from "zod";
import logger from "@server/logger"; import logger from "@server/logger";
import semver from "semver"; import semver from "semver";
import { getValidCertificatesForDomains } from "#dynamic/lib/certificates"; import { getValidCertificatesForDomains } from "#dynamic/lib/certificates";
import { lockManager } from "#dynamic/lib/lock";
interface IPRange { interface IPRange {
start: bigint; start: bigint;
@@ -327,121 +328,146 @@ export async function getNextAvailableClientSubnet(
orgId: string, orgId: string,
transaction: Transaction | typeof db = db transaction: Transaction | typeof db = db
): Promise<string> { ): Promise<string> {
const [org] = await transaction return await lockManager.withLock(
.select() `client-subnet-allocation:${orgId}`,
.from(orgs) async () => {
.where(eq(orgs.orgId, orgId)); const [org] = await transaction
.select()
.from(orgs)
.where(eq(orgs.orgId, orgId));
if (!org) { if (!org) {
throw new Error(`Organization with ID ${orgId} not found`); throw new Error(`Organization with ID ${orgId} not found`);
} }
if (!org.subnet) { if (!org.subnet) {
throw new Error(`Organization with ID ${orgId} has no subnet defined`); throw new Error(
} `Organization with ID ${orgId} has no subnet defined`
);
}
const existingAddressesSites = await transaction const existingAddressesSites = await transaction
.select({ .select({
address: sites.address address: sites.address
}) })
.from(sites) .from(sites)
.where(and(isNotNull(sites.address), eq(sites.orgId, orgId))); .where(and(isNotNull(sites.address), eq(sites.orgId, orgId)));
const existingAddressesClients = await transaction const existingAddressesClients = await transaction
.select({ .select({
address: clients.subnet address: clients.subnet
}) })
.from(clients) .from(clients)
.where(and(isNotNull(clients.subnet), eq(clients.orgId, orgId))); .where(
and(isNotNull(clients.subnet), eq(clients.orgId, orgId))
);
const addresses = [ const addresses = [
...existingAddressesSites.map( ...existingAddressesSites.map(
(site) => `${site.address?.split("/")[0]}/32` (site) => `${site.address?.split("/")[0]}/32`
), // we are overriding the 32 so that we pick individual addresses in the subnet of the org for the site and the client even though they are stored with the /block_size of the org ), // we are overriding the 32 so that we pick individual addresses in the subnet of the org for the site and the client even though they are stored with the /block_size of the org
...existingAddressesClients.map( ...existingAddressesClients.map(
(client) => `${client.address.split("/")}/32` (client) => `${client.address.split("/")}/32`
) )
].filter((address) => address !== null) as string[]; ].filter((address) => address !== null) as string[];
const subnet = findNextAvailableCidr(addresses, 32, org.subnet); // pick the sites address in the org const subnet = findNextAvailableCidr(addresses, 32, org.subnet); // pick the sites address in the org
if (!subnet) { if (!subnet) {
throw new Error("No available subnets remaining in space"); throw new Error("No available subnets remaining in space");
} }
return subnet; return subnet;
}
);
} }
export async function getNextAvailableAliasAddress( export async function getNextAvailableAliasAddress(
orgId: string, orgId: string,
trx: Transaction | typeof db = db trx: Transaction | typeof db = db
): Promise<string> { ): Promise<string> {
const [org] = await trx.select().from(orgs).where(eq(orgs.orgId, orgId)); return await lockManager.withLock(
`alias-address-allocation:${orgId}`,
async () => {
const [org] = await trx
.select()
.from(orgs)
.where(eq(orgs.orgId, orgId));
if (!org) { if (!org) {
throw new Error(`Organization with ID ${orgId} not found`); throw new Error(`Organization with ID ${orgId} not found`);
} }
if (!org.subnet) { if (!org.subnet) {
throw new Error(`Organization with ID ${orgId} has no subnet defined`); throw new Error(
} `Organization with ID ${orgId} has no subnet defined`
);
}
if (!org.utilitySubnet) { if (!org.utilitySubnet) {
throw new Error( throw new Error(
`Organization with ID ${orgId} has no utility subnet defined` `Organization with ID ${orgId} has no utility subnet defined`
); );
} }
const existingAddresses = await trx const existingAddresses = await trx
.select({ .select({
aliasAddress: siteResources.aliasAddress aliasAddress: siteResources.aliasAddress
}) })
.from(siteResources) .from(siteResources)
.where( .where(
and( and(
isNotNull(siteResources.aliasAddress), isNotNull(siteResources.aliasAddress),
eq(siteResources.orgId, orgId) eq(siteResources.orgId, orgId)
) )
); );
const addresses = [ const addresses = [
...existingAddresses.map( ...existingAddresses.map(
(site) => `${site.aliasAddress?.split("/")[0]}/32` (site) => `${site.aliasAddress?.split("/")[0]}/32`
), ),
// reserve a /29 for the dns server and other stuff // reserve a /29 for the dns server and other stuff
`${org.utilitySubnet.split("/")[0]}/29` `${org.utilitySubnet.split("/")[0]}/29`
].filter((address) => address !== null) as string[]; ].filter((address) => address !== null) as string[];
let subnet = findNextAvailableCidr(addresses, 32, org.utilitySubnet); let subnet = findNextAvailableCidr(
if (!subnet) { addresses,
throw new Error("No available subnets remaining in space"); 32,
} org.utilitySubnet
);
if (!subnet) {
throw new Error("No available subnets remaining in space");
}
// remove the cidr // remove the cidr
subnet = subnet.split("/")[0]; subnet = subnet.split("/")[0];
return subnet; return subnet;
}
);
} }
export async function getNextAvailableOrgSubnet(): Promise<string> { export async function getNextAvailableOrgSubnet(): Promise<string> {
const existingAddresses = await db return await lockManager.withLock("org-subnet-allocation", async () => {
.select({ const existingAddresses = await db
subnet: orgs.subnet .select({
}) subnet: orgs.subnet
.from(orgs) })
.where(isNotNull(orgs.subnet)); .from(orgs)
.where(isNotNull(orgs.subnet));
const addresses = existingAddresses.map((org) => org.subnet!); const addresses = existingAddresses.map((org) => org.subnet!);
const subnet = findNextAvailableCidr( const subnet = findNextAvailableCidr(
addresses, addresses,
config.getRawConfig().orgs.block_size, config.getRawConfig().orgs.block_size,
config.getRawConfig().orgs.subnet_group config.getRawConfig().orgs.subnet_group
); );
if (!subnet) { if (!subnet) {
throw new Error("No available subnets remaining in space"); throw new Error("No available subnets remaining in space");
} }
return subnet; return subnet;
});
} }
export function generateRemoteSubnets( export function generateRemoteSubnets(