import {Injectable, Model, Field, FieldType, TypeTag, Maybe, collect, ErrorWithContext} from '@extollo/lib' import * as IPCIDR from 'ip-cidr' import {IpAddress, Subnet} from '../types' import {Node} from './Node.model' /** * IpRange Model * ----------------------------------- * Represents an available IP address pool from which we can allocate node addresses. */ @Injectable() export class IpRange extends Model { protected static table = 'p5x_ip_ranges' protected static key = 'id' public static async getDefault(): Promise { const ipRange = await this.query().first() if ( !ipRange ) { throw new Error('Unable to find default IP range!') } return ipRange } @Field(FieldType.serial) public id?: number @Field(FieldType.varchar) public name!: string @Field(FieldType.varchar, 'start_ip') public startIp!: IpAddress @Field(FieldType.varchar, 'end_ip') public endIp!: IpAddress @Field(FieldType.int) public subnet!: Subnet @Field(FieldType.varchar, 'gateway_ip') public gatewayIp!: IpAddress /** Get the next IP address in this range that has not already been allocated to a node. */ public async getNextAvailable(): Promise> { // Generate the address pool const cidr = new IPCIDR(`${this.gatewayIp}/${this.subnet}`) const possibleIps = collect(cidr.toArray({ from: this.startIp, to: this.endIp }) as IpAddress[]) .filterOut(x => x.endsWith('.0')) // Look up already-allocated addresses const allocatedIps = await Node.query().get().pluck('assignedIp') // Find the first available address return possibleIps.firstWhere(ip => !allocatedIps.includes(ip)) } /** Like `getNextAvailable(...)`, but it will throw if the range is exhausted */ public async getNextAvailableOrFail(): Promise { const ip = await this.getNextAvailable() if ( !ip ) { throw new ErrorWithContext('Unable to get next available IP from range: is range exhausted?', { range: this.toObject(), }) } return ip } }