mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-07 22:56:07 +08:00
feat: support network boot (PXE) for VM interfaces (#1176)
Add an "Enable network boot (PXE)" option to bridge network interfaces and a VM-level choice to place network boot devices before or after volumes. The form now owns interface boot order: enabled NICs get sequential boot orders ahead of or behind the volumes, and a stale bootOrder is no longer inherited from the old spec. This also fixes editing a VM whose interface bootOrder was set in YAML (the documented workaround): the form used to renumber disks from 1 while keeping the NIC's bootOrder, producing a duplicate that KubeVirt rejects. The boot order shown on the VM, snapshot and backup detail pages now includes network interfaces. Related: harvester/harvester#3325 Signed-off-by: Alejandro Bonilla <abonilla@suse.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
528255dea4
commit
5af181f5b6
@@ -160,6 +160,7 @@ export default {
|
||||
imageId: '',
|
||||
diskRows: [],
|
||||
networkRows: [],
|
||||
networkBootFirst: true,
|
||||
filesystemRows: [],
|
||||
machineType: '',
|
||||
machineTypes: [],
|
||||
@@ -384,6 +385,7 @@ export default {
|
||||
const diskRows = this.getDiskRows(vm, volumeBackups);
|
||||
|
||||
const networkRows = this.getNetworkRows(vm, { fromTemplate, init });
|
||||
const networkBootFirst = this.isNetworkBootFirst(spec);
|
||||
const hasCreateVolumes = this.getHasCreatedVolumes(spec) || [];
|
||||
|
||||
let { userData = undefined, networkData = undefined } = this.getCloudInitNoCloud(spec);
|
||||
@@ -461,6 +463,7 @@ export default {
|
||||
|
||||
this['hasCreateVolumes'] = hasCreateVolumes;
|
||||
this['networkRows'] = networkRows;
|
||||
this['networkBootFirst'] = networkBootFirst;
|
||||
this['imageId'] = imageId;
|
||||
|
||||
this['diskRows'] = diskRows;
|
||||
@@ -676,12 +679,29 @@ export default {
|
||||
model: I.model,
|
||||
networkName: isPod ? MANAGEMENT_NETWORK : network?.multus?.networkName,
|
||||
staticIp: annotations[`${ staticIpPrefix }${ I.name }`] || '',
|
||||
networkBoot: !!I.bootOrder,
|
||||
};
|
||||
});
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
/**
|
||||
* Network boot devices are placed either before or after all volumes in the boot sequence.
|
||||
* Derive which one from an existing spec, so a round trip through the form keeps it.
|
||||
*/
|
||||
isNetworkBootFirst(spec) {
|
||||
const devices = spec?.template?.spec?.domain?.devices || {};
|
||||
const nicOrders = (devices.interfaces || []).map((I) => I.bootOrder).filter(Boolean);
|
||||
const diskOrders = (devices.disks || []).map((D) => D.bootOrder).filter(Boolean);
|
||||
|
||||
if (nicOrders.length === 0 || diskOrders.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Math.min(...nicOrders) < Math.min(...diskOrders);
|
||||
},
|
||||
|
||||
parseVM() {
|
||||
this.userData = this.getUserData({ osType: this.osType, installAgent: this.installAgent });
|
||||
this.parseOther();
|
||||
@@ -894,9 +914,10 @@ export default {
|
||||
const disks = [];
|
||||
const volumes = [];
|
||||
const volumeClaimTemplates = [];
|
||||
const bootOrderOffset = this.networkBootFirst ? this.networkRows.filter((R) => R.networkBoot).length : 0;
|
||||
|
||||
disk.forEach( (R, index) => {
|
||||
const _disk = this.parseDisk(R, index);
|
||||
const _disk = this.parseDisk(R, index + bootOrderOffset);
|
||||
|
||||
disks.push(_disk);
|
||||
|
||||
@@ -1154,9 +1175,11 @@ export default {
|
||||
const networks = [];
|
||||
const interfaces = [];
|
||||
|
||||
let bootOrder = this.networkBootFirst ? 1 : this.diskRows.length + 1;
|
||||
|
||||
networkRow.forEach( (R) => {
|
||||
const _network = this.parseNetwork(R);
|
||||
const _interface = this.parseInterface(R);
|
||||
const _interface = this.parseInterface(R, R.networkBoot ? bootOrder++ : undefined);
|
||||
|
||||
networks.push(_network);
|
||||
interfaces.push(_interface);
|
||||
@@ -1412,7 +1435,7 @@ export default {
|
||||
return arr.map( (id) => this.getSSHValue(id)).filter( (O) => O !== undefined);
|
||||
},
|
||||
|
||||
parseInterface(R) {
|
||||
parseInterface(R, bootOrder) {
|
||||
const _interface = {};
|
||||
const type = R.type;
|
||||
|
||||
@@ -1422,6 +1445,10 @@ export default {
|
||||
_interface.macAddress = R.macAddress;
|
||||
}
|
||||
|
||||
if (bootOrder) {
|
||||
_interface.bootOrder = bootOrder;
|
||||
}
|
||||
|
||||
_interface.model = R.model;
|
||||
_interface.name = R.name;
|
||||
|
||||
@@ -1929,6 +1956,11 @@ export default {
|
||||
delete merged['bridge'];
|
||||
}
|
||||
|
||||
// boot order is owned by the form, don't inherit a stale one from the old spec
|
||||
if (!iface.bootOrder) {
|
||||
delete merged.bootOrder;
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user