From 6700b2055eafa9857b84aef112c162fa056229d5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 14:15:27 +0800 Subject: [PATCH] feat: add support for configuring transparent hugepages (#414) (#598) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add support for configuring transparent hugepages Related-to: https://github.com/harvester/harvester/issues/5006 * fix: return empty object if hugepages can't be found for node Related-to: https://github.com/harvester/harvester/issues/5006 --------- (cherry picked from commit 30de2b1a185ccc2a3ec159e220de742dd2156229) Signed-off-by: Tim Serong Co-authored-by: Tim Serong Co-authored-by: Moritz Röhrich Co-authored-by: Andy Lee --- pkg/harvester/config/doc-links.js | 1 + .../HarvesterHugepages.vue | 119 +++++++++++++ .../detail/harvesterhci.io.host/index.vue | 18 ++ .../HarvesterHugepages.vue | 157 ++++++++++++++++++ .../edit/harvesterhci.io.host/index.vue | 19 +++ pkg/harvester/l10n/en-us.yaml | 15 ++ pkg/harvester/types.ts | 1 + 7 files changed, 330 insertions(+) create mode 100644 pkg/harvester/detail/harvesterhci.io.host/HarvesterHugepages.vue create mode 100644 pkg/harvester/edit/harvesterhci.io.host/HarvesterHugepages.vue diff --git a/pkg/harvester/config/doc-links.js b/pkg/harvester/config/doc-links.js index 1542d906..a76df6fb 100644 --- a/pkg/harvester/config/doc-links.js +++ b/pkg/harvester/config/doc-links.js @@ -9,4 +9,5 @@ export const DOC = { SUPPORT_BUNDLE_NAMESPACES: `/advanced/index/#support-bundle-namespaces`, VPC_CONFIGURATION_EXAMPLES: `/networking/kubeovn-vpc#vpc-peering-configuration-examples`, NETWORK_POLICY: `/networking/kubeovn-vm-isolation/#network-policies`, + TRANSPARENT_HUGEPAGES: `https://docs.kernel.org/admin-guide/mm/transhuge.html`, }; diff --git a/pkg/harvester/detail/harvesterhci.io.host/HarvesterHugepages.vue b/pkg/harvester/detail/harvesterhci.io.host/HarvesterHugepages.vue new file mode 100644 index 00000000..b4f60394 --- /dev/null +++ b/pkg/harvester/detail/harvesterhci.io.host/HarvesterHugepages.vue @@ -0,0 +1,119 @@ + + + diff --git a/pkg/harvester/detail/harvesterhci.io.host/index.vue b/pkg/harvester/detail/harvesterhci.io.host/index.vue index a2734e15..4a8a9c45 100644 --- a/pkg/harvester/detail/harvesterhci.io.host/index.vue +++ b/pkg/harvester/detail/harvesterhci.io.host/index.vue @@ -27,6 +27,7 @@ import Instance from './VirtualMachineInstance'; import Disk from './HarvesterHostDisk'; import VlanStatus from './VlanStatus'; import HarvesterKsmtuned from './HarvesterKsmtuned.vue'; +import HarvesterHugepages from './HarvesterHugepages.vue'; import HarvesterSeeder from './HarvesterSeeder'; const LONGHORN_SYSTEM = 'longhorn-system'; @@ -46,6 +47,7 @@ export default { VlanStatus, LabelValue, HarvesterKsmtuned, + HarvesterHugepages, Loading, SortableTable, HarvesterSeeder, @@ -209,6 +211,12 @@ export default { return !!this.$store.getters[`${ inStore }/schemaFor`](HCI.KSTUNED); }, + hasHugepagesSchema() { + const inStore = this.$store.getters['currentProduct'].inStore; + + return !!this.$store.getters[`${ inStore }/schemaFor`](HCI.HUGEPAGES); + }, + hasBlockDevicesSchema() { return !!this.$store.getters['harvester/schemaFor'](HCI.BLOCK_DEVICE); }, @@ -468,6 +476,16 @@ export default { /> + + + + +import LabeledSelect from '@shell/components/form/LabeledSelect.vue'; +import { HCI } from '../../types'; +import { DOC } from '../../config/doc-links'; + +export const hugepagesTHPEnabledMode = [{ + label: 'Always', + value: 'always', +}, { + label: 'Madvise', + value: 'madvise', +}, { + label: 'Never', + value: 'never', +}]; + +export const hugepagesTHPShmemEnabledMode = [{ + label: 'Always', + value: 'always', +}, { + label: 'Within Size', + value: 'within_size', +}, { + label: 'Advise', + value: 'advise', +}, { + label: 'Never', + value: 'never', +}, { + label: 'Deny', + value: 'deny', +}, { + label: 'Force', + value: 'force', +}]; + +export const hugepagesTHPDefragMode = [{ + label: 'Always', + value: 'always', +}, { + label: 'Defer', + value: 'defer', +}, { + label: 'Defer+Madvise', + value: 'defer+madvise', +}, { + label: 'Madvise', + value: 'madvise', +}, { + label: 'Never', + value: 'never' +}]; + +export default { + name: 'HarvesterHugepages', + components: { LabeledSelect }, + + props: { + node: { + type: Object, + required: true, + }, + + registerBeforeHook: { + type: Function, + required: true, + }, + }, + + computed: { + docsTransparentHugepagesLink() { + return DOC.TRANSPARENT_HUGEPAGES; + }, + }, + + methods: { + async saveHugepages() { + this.hugepages['spec'] = this.spec; + + await this.hugepages.save().catch((reason) => { + if (reason?.type === 'error') { + this.$store.dispatch('growl/error', { + title: this.t('harvester.notification.title.error'), + message: reason?.message, + }, { root: true }); + + return Promise.reject(new Error('saveHugepages error')); + } + }); + }, + }, + + async fetch() { + const inStore = this.$store.getters['currentProduct'].inStore; + + const hash = await this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.HUGEPAGES }); + + this.hugepages = hash.find((node) => { + return node.id === this.node.id; + }); + this.spec = this.hugepages.spec; + }, + + data() { + return { + hugepages: {}, + spec: { transparent: {} }, + hugepagesTHPEnabledMode, + hugepagesTHPShmemEnabledMode, + hugepagesTHPDefragMode, + }; + }, + + created() { + this.registerBeforeHook(this.saveHugepages, 'saveHugepages'); + }, +}; + + + diff --git a/pkg/harvester/edit/harvesterhci.io.host/index.vue b/pkg/harvester/edit/harvesterhci.io.host/index.vue index 3f042bb1..4fd44bec 100644 --- a/pkg/harvester/edit/harvesterhci.io.host/index.vue +++ b/pkg/harvester/edit/harvesterhci.io.host/index.vue @@ -28,6 +28,7 @@ import { HCI } from '../../types'; import HarvesterDisk from './HarvesterDisk'; import HarvesterSeeder from './HarvesterSeeder'; import HarvesterKsmtuned from './HarvesterKsmtuned'; +import HarvesterHugepages from './HarvesterHugepages'; import Tags from '../../components/DiskTags'; import { LVM_DRIVER } from '../../models/harvester/storage.k8s.io.storageclass'; import isEqual from 'lodash/isEqual'; @@ -50,6 +51,7 @@ export default { ArrayListGrouped, HarvesterDisk, HarvesterKsmtuned, + HarvesterHugepages, ButtonDropdown, KeyValue, Banner, @@ -225,6 +227,12 @@ export default { return !!this.$store.getters[`${ inStore }/schemaFor`](HCI.KSTUNED); }, + hasHugepagesSchema() { + const inStore = this.$store.getters['currentProduct'].inStore; + + return !!this.$store.getters[`${ inStore }/schemaFor`](HCI.HUGEPAGES); + }, + hasBlockDevicesSchema() { const inStore = this.$store.getters['currentProduct'].inStore; @@ -647,6 +655,17 @@ export default { + + + + enabled: Enabled + shmemEnabled: Shared Memory Enabled + defrag: Defragmentation + status: + anon: Anonymous Hugepages (bytes) + size: Default Hugepage Size (bytes) + total: Total Hugepages + free: Free Hugepages + rsvd: Reserved Hugepages + surp: Surplus Hugepages disk: add: Add Disk path: diff --git a/pkg/harvester/types.ts b/pkg/harvester/types.ts index b4f7c6ea..6f7bd64b 100644 --- a/pkg/harvester/types.ts +++ b/pkg/harvester/types.ts @@ -37,6 +37,7 @@ export const HCI = { STORAGE: 'harvesterhci.io.storage', RESOURCE_QUOTA: 'harvesterhci.io.resourcequota', KSTUNED: 'node.harvesterhci.io.ksmtuned', + HUGEPAGES: 'node.harvesterhci.io.hugepage', PCI_DEVICE: 'devices.harvesterhci.io.pcidevice', PCI_CLAIM: 'devices.harvesterhci.io.pcideviceclaim', SR_IOV: 'devices.harvesterhci.io.sriovnetworkdevice',