mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-20 07:45:20 +00:00
Add locks to allocations
This commit is contained in:
202
server/lib/ip.ts
202
server/lib/ip.ts
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user