[WIP] finish implementing cross pve-host disk transfers and mounting

This commit is contained in:
Garrett Mills 2024-10-01 22:57:49 -04:00
parent 9eddb96402
commit 0545c8337b
3 changed files with 69 additions and 6 deletions

View File

@ -44,6 +44,8 @@ export class Volumes extends Controller {
mountpoint = rawMountpoint mountpoint = rawMountpoint
} }
console.log('Mount options', this.request.input('options'))
vol = await this.provisioner.mountVolume(vol, mountpoint) vol = await this.provisioner.mountVolume(vol, mountpoint)
return vol.toAPI() return vol.toAPI()
} }
@ -54,7 +56,24 @@ export class Volumes extends Controller {
} }
public async transfer(vol: Volume, toNode: Node) { public async transfer(vol: Volume, toNode: Node) {
const fromNode = await vol.getNode()
if ( fromNode.pveId === toNode.pveId ) {
// The volume is already attached to the target node, so we're done
} else if ( fromNode.pveHost === toNode.pveHost ) {
// If the from/to nodes are on the same physical host, just transfer the volume directly
vol = await this.provisioner.transferVolume(vol, toNode) vol = await this.provisioner.transferVolume(vol, toNode)
} else {
// If the nodes are on different physical hosts, we need to create a temporary container
// on shared storage to attach the volume to. We'll then migrate that container to the
// target physical host.
let carrier = await this.provisioner.provisionCarrierContainer(fromNode)
vol = await this.provisioner.transferVolume(vol, carrier)
carrier = await this.provisioner.migrateNode(carrier, toNode.pveHost)
vol = await this.provisioner.transferVolume(vol, toNode)
await this.provisioner.unprovisionCarrierContainer(carrier)
}
return vol.toAPI() return vol.toAPI()
} }
} }

View File

@ -55,7 +55,7 @@ export class ConfigLines extends Collection<string> {
@Injectable() @Injectable()
export class Node extends Model<Node> { export class Node extends Model<Node> {
protected static table = 'p5x_nodes' protected static table = 'p5x_nodes'
protected static key = 'node_id' protected static key = 'id'
public static async getMaster(): Promise<Node> { public static async getMaster(): Promise<Node> {
const master = await Node.query<Node>() const master = await Node.query<Node>()

View File

@ -208,14 +208,14 @@ export class Provisioner {
return volume return volume
} }
public async provisionCarrierContainer(node: Node): Promise<string> { public async provisionCarrierContainer(node: Node): Promise<Node> {
// Get the Proxmox API! // Get the Proxmox API!
const proxmox = await this.getApi() const proxmox = await this.getApi()
const nodeName = node.pveHost.split('/')[1] const nodeName = node.pveHost.split('/')[1]
const pveHost = proxmox.nodes.$(nodeName) const pveHost = proxmox.nodes.$(nodeName)
// Ensure the empty filesystem template exists // Ensure the empty filesystem template exists
const host = await node.getHost() // fixme this is wrong -- need pve host const host = await this.getPVEHost(node.unqualifiedPVEHost())
const fs = await host.getFilesystem() const fs = await host.getFilesystem()
const stat = await fs.stat({ storePath: '/var/lib/vz/template/cache/p5x-empty.tar.xz' }) const stat = await fs.stat({ storePath: '/var/lib/vz/template/cache/p5x-empty.tar.xz' })
if ( !stat.exists ) { if ( !stat.exists ) {
@ -239,7 +239,7 @@ export class Provisioner {
cores: 1, cores: 1,
description: 'Temporary container managed by P5x', description: 'Temporary container managed by P5x',
hostname: name, hostname: name,
memory: 10, // in MB memory: 16, // in MB
start: false, start: false,
storage: await Setting.loadOneRequired('pveStoragePool'), storage: await Setting.loadOneRequired('pveStoragePool'),
tags: 'p5x', tags: 'p5x',
@ -249,7 +249,30 @@ export class Provisioner {
await this.waitForNodeTask(nodeName, createUPID) await this.waitForNodeTask(nodeName, createUPID)
this.logging.info(`Created carrier container: ${name}`) this.logging.info(`Created carrier container: ${name}`)
return name const carrierNode = this.container.makeNew<Node>(Node)
carrierNode.pveId = carrierVMID
carrierNode.hostname = name
carrierNode.assignedIp = carrierIP
carrierNode.assignedSubnet = ipRange.subnet
carrierNode.pveHost = node.pveHost
await carrierNode.save()
return carrierNode
}
public async unprovisionCarrierContainer(node: Node): Promise<void> {
this.logging.info(`Deleting carrier container ${node.pveId}`)
const api = await this.getApi()
const upid = await api
.nodes.$(node.unqualifiedPVEHost())
.lxc.$(node.pveId)
.$delete({
purge: true,
'destroy-unreferenced-disks': true,
})
await this.waitForNodeTask(node.unqualifiedPVEHost(), upid)
await node.delete()
this.logging.success(`Deleted carrier container ${node.pveId}`)
} }
public async provisionNode(group: HostGroup): Promise<Node> { public async provisionNode(group: HostGroup): Promise<Node> {
@ -615,6 +638,27 @@ export class Provisioner {
return vol return vol
} }
public async migrateNode(node: Node, qualifiedPveHost: string): Promise<Node> {
this.logging.info(`Migrating node ${node.pveId} from ${node.pveHost} to ${qualifiedPveHost}`)
const api = await this.getApi()
const originalUnqualifiedPveHost = node.unqualifiedPVEHost()
node.pveHost = qualifiedPveHost
const upid = await api
.nodes.$(originalUnqualifiedPveHost)
.lxc.$(node.pveId)
.migrate.$post({
target: node.unqualifiedPVEHost(),
})
this.logging.info(`Waiting for migrate task: ${upid}`)
await this.waitForNodeTask(originalUnqualifiedPveHost, upid)
await node.save()
this.logging.success(`Migrated node ${node.pveId} from ${node.pveHost} to ${qualifiedPveHost}`)
return node
}
public async getPVEHost(pveHost: string): Promise<Host> { public async getPVEHost(pveHost: string): Promise<Host> {
const api = await this.getApi() const api = await this.getApi()
const ifaces = await api.nodes.$(pveHost).network.$get() const ifaces = await api.nodes.$(pveHost).network.$get()