mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-06-13 13:32:20 +00:00
when implementing the first version pci device passthrough, we didn't have our own device plugin, then resource name was changed after upgrade. Right now, we already have our own device plugin, which we can control resource name by our ourselves. So, we can remove old mechanism. Signed-off-by: Jack Yu <jack.yu@suse.com>
300 lines
8.3 KiB
Vue
300 lines
8.3 KiB
Vue
<script>
|
|
import { _EDIT } from '@shell/config/query-params';
|
|
import { allHash } from '@shell/utils/promise';
|
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
import Banner from '@components/Banner/Banner.vue';
|
|
import remove from 'lodash/remove';
|
|
import { set } from '@shell/utils/object';
|
|
import { HCI } from '../../../types';
|
|
import DeviceList from './DeviceList';
|
|
import CompatibilityMatrix from '../CompatibilityMatrix';
|
|
import MessageLink from '@shell/components/MessageLink';
|
|
|
|
export default {
|
|
name: 'VirtualMachinePCIDevices',
|
|
components: {
|
|
LabeledSelect,
|
|
DeviceList,
|
|
CompatibilityMatrix,
|
|
Banner,
|
|
MessageLink
|
|
},
|
|
props: {
|
|
mode: {
|
|
type: String,
|
|
default: _EDIT
|
|
},
|
|
// spec.template.spec
|
|
value: {
|
|
type: Object,
|
|
default: () => {}
|
|
},
|
|
|
|
vm: {
|
|
type: Object,
|
|
default: () => {}
|
|
}
|
|
},
|
|
|
|
async fetch() {
|
|
const hash = {
|
|
// claims fetched here so synchronous pciDevice model property works
|
|
pciDevices: this.$store.dispatch('harvester/findAll', { type: HCI.PCI_DEVICE }),
|
|
claims: this.$store.dispatch('harvester/findAll', { type: HCI.PCI_CLAIM }),
|
|
vms: this.$store.dispatch(`harvester/findAll`, { type: HCI.VM })
|
|
|
|
};
|
|
|
|
const res = await allHash(hash);
|
|
|
|
for (const key in res) {
|
|
this[key] = res[key];
|
|
}
|
|
|
|
const selectedDevices = [];
|
|
|
|
const vmDevices = this.value?.domain?.devices?.hostDevices || [];
|
|
const vmDeviceNames = vmDevices.map(({ name }) => name);
|
|
|
|
this.pciDevices.forEach((row) => {
|
|
row.allowDisable = !vmDeviceNames.includes(row.metadata.name);
|
|
});
|
|
|
|
vmDevices.forEach(({ name }) => {
|
|
if (this.enabledDevices.find((device) => device?.metadata?.name === name)) {
|
|
selectedDevices.push(name);
|
|
}
|
|
});
|
|
|
|
this.selectedDevices = selectedDevices;
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
pciDevices: [],
|
|
claims: [],
|
|
vms: [],
|
|
selectedDevices: [],
|
|
pciDeviceSchema: this.$store.getters['harvester/schemaFor'](HCI.PCI_DEVICE),
|
|
showMatrix: false,
|
|
};
|
|
},
|
|
|
|
watch: {
|
|
selectedDevices(neu) {
|
|
const formatted = neu.map((selectedDevice) => {
|
|
const deviceCRD = this.enabledDevices.find((device) => device.metadata.name === selectedDevice);
|
|
const deviceName = deviceCRD?.status?.resourceName;
|
|
|
|
return {
|
|
deviceName,
|
|
name: deviceCRD?.metadata.name,
|
|
};
|
|
});
|
|
|
|
const devices = [
|
|
...this.otherDevices(this.value?.domain?.devices?.hostDevices || []),
|
|
...formatted,
|
|
];
|
|
|
|
set(this.value.domain.devices, 'hostDevices', devices);
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
// user can only select devices for whcih pci passthrough is enabled/claimed by them - determined by finding the associated passthrough CRD
|
|
enabledDevices() {
|
|
return this.pciDevices.filter((device) => {
|
|
return device.isEnabled && device.claimedByMe;
|
|
}) || [];
|
|
},
|
|
|
|
// find devices in use by other VMs and sum the number of each device already in use
|
|
devicesInUse() {
|
|
const inUse = this.vms.reduce((inUse, vm) => {
|
|
// dont count devices in this vm as 'disabled' if they're just being used in the vm currently being edited
|
|
if (vm.metadata.name === this.vm?.metadata?.name) {
|
|
return inUse;
|
|
}
|
|
|
|
const hostDevices = vm?.hostDevices || [];
|
|
|
|
hostDevices.forEach((device) => {
|
|
inUse[device.name] = { usedBy: [vm.metadata.name] };
|
|
});
|
|
|
|
return inUse;
|
|
}, {});
|
|
|
|
return inUse;
|
|
},
|
|
|
|
toVGpuDevicesPage() {
|
|
return {
|
|
name: 'harvester-c-cluster-resource',
|
|
params: { cluster: this.$store.getters['clusterId'], resource: HCI.VGPU_DEVICE },
|
|
};
|
|
},
|
|
|
|
devicesByNode() {
|
|
return this.enabledDevices?.reduce((acc, device) => {
|
|
const nodeName = device.status?.nodeName;
|
|
|
|
if (nodeName) {
|
|
if (!acc[nodeName]) {
|
|
acc[nodeName] = [];
|
|
} else {
|
|
acc[nodeName].push(device);
|
|
}
|
|
}
|
|
|
|
return acc;
|
|
}, {});
|
|
},
|
|
|
|
// determine which nodes contain all devices selected
|
|
compatibleNodes() {
|
|
const out = [...Object.keys(this.devicesByNode)];
|
|
|
|
this.selectedDevices.forEach((deviceUid) => {
|
|
remove(out, (nodeName) => {
|
|
const device = this.enabledDevices.find((deviceCRD) => deviceCRD.metadata.name === deviceUid);
|
|
|
|
return device.status.nodeName !== nodeName;
|
|
});
|
|
});
|
|
|
|
return out;
|
|
},
|
|
|
|
// format an array of available devices for the dropdown
|
|
deviceOpts() {
|
|
const filteredOptions = this.enabledDevices.filter((deviceCRD) => {
|
|
if (this.selectedDevices.length > 0) {
|
|
const selectedDevice = this.enabledDevices.find((device) => device.metadata.name === this.selectedDevices[0]);
|
|
|
|
return !this.devicesInUse[deviceCRD?.metadata.name] && deviceCRD.status.nodeName === selectedDevice.status.nodeName;
|
|
}
|
|
|
|
return !this.devicesInUse[deviceCRD?.metadata.name];
|
|
});
|
|
|
|
return filteredOptions.map((deviceCRD) => {
|
|
return {
|
|
value: deviceCRD?.metadata.name,
|
|
label: deviceCRD?.metadata.name,
|
|
displayLabel: deviceCRD?.status?.resourceName,
|
|
};
|
|
});
|
|
},
|
|
|
|
},
|
|
|
|
methods: {
|
|
otherDevices(vmDevices) {
|
|
return vmDevices.filter((device) => !this.pciDevices.find((pci) => device.name === pci.name));
|
|
},
|
|
|
|
nodeNameFromUid(uid) {
|
|
for (const deviceUid in this.uniqueDevices) {
|
|
const nodes = this.uniqueDevices[deviceUid].nodes;
|
|
const thisNode = nodes.find((node) => node.systemUUID === uid);
|
|
|
|
if (thisNode) {
|
|
return thisNode.name;
|
|
}
|
|
}
|
|
},
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<div class="row">
|
|
<div class="col span-12">
|
|
<Banner color="info">
|
|
<MessageLink
|
|
:to="toVGpuDevicesPage"
|
|
prefix-label="harvester.pci.howToUseDeviceInVMCreation.prefix"
|
|
middle-label="harvester.pci.howToUseDeviceInVMCreation.middle"
|
|
suffix-label="harvester.pci.howToUseDeviceInVMCreation.suffix"
|
|
/>
|
|
</Banner>
|
|
<Banner
|
|
v-if="selectedDevices.length > 0"
|
|
color="info"
|
|
>
|
|
<t k="harvester.pci.deviceInTheSameHost" />
|
|
</Banner>
|
|
</div>
|
|
</div>
|
|
<template v-if="enabledDevices.length">
|
|
<div class="row">
|
|
<div class="col span-6">
|
|
<LabeledSelect
|
|
v-model:value="selectedDevices"
|
|
label="Available PCI Devices"
|
|
searchable
|
|
multiple
|
|
taggable
|
|
:options="deviceOpts"
|
|
:mode="mode"
|
|
>
|
|
<template #option="option">
|
|
<span>{{ option.value }} <span class="text-label">({{ option.displayLabel }})</span></span>
|
|
</template>
|
|
</LabeledSelect>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-if="compatibleNodes.length && selectedDevices.length"
|
|
class="row"
|
|
>
|
|
<div class="col span-12 text-muted">
|
|
Compatible hosts:
|
|
<!-- eslint-disable-next-line vue/no-parsing-error -->
|
|
<span
|
|
v-for="(node, idx) in compatibleNodes"
|
|
:key="idx"
|
|
>{{ node }}{{ idx < compatibleNodes.length-1 ? ', ' : '' }}</span>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-else-if="selectedDevices.length"
|
|
class="text-error"
|
|
>
|
|
{{ t('harvester.pci.impossibleSelection') }}
|
|
</div>
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm role-link pl-0"
|
|
@click="e=>{showMatrix = !showMatrix; e.target.blur()}"
|
|
>
|
|
{{ showMatrix ? t('harvester.pci.hideCompatibility') : t('harvester.pci.showCompatibility') }}
|
|
</button>
|
|
<div
|
|
v-if="showMatrix"
|
|
class="row mt-20"
|
|
>
|
|
<div class="col span-12">
|
|
<CompatibilityMatrix
|
|
:enabled-devices="enabledDevices"
|
|
:devices-by-node="devicesByNode"
|
|
:devices-in-use="devicesInUse"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<div class="row mt-20">
|
|
<div class="col span-12">
|
|
<DeviceList
|
|
:schema="pciDeviceSchema"
|
|
:devices="pciDevices"
|
|
@submit.prevent
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|