mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-12 00:56:05 +08:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f13aadeed | ||
|
|
da36db143f | ||
|
|
5af181f5b6 |
@@ -53,7 +53,7 @@ jobs:
|
||||
- name: Setup Helm
|
||||
uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # v3
|
||||
with:
|
||||
version: v3.22.0
|
||||
version: v3.8.0
|
||||
|
||||
- name: Setup Nodejs with yarn install
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
- name: Setup Helm
|
||||
uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # v3
|
||||
with:
|
||||
version: v3.22.0
|
||||
version: v3.8.0
|
||||
|
||||
- name: Build Helm charts
|
||||
run: |
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
"@xterm/addon-webgl": "0.19.0",
|
||||
"@xterm/addon-canvas": "0.7.0",
|
||||
"node-polyfill-webpack-plugin": "^3.0.0",
|
||||
"elkjs": "^0.12.0",
|
||||
"elkjs": "^0.11.0",
|
||||
"vue-draggable-next": "^2.2.1",
|
||||
"yaml": "^2.5.1"
|
||||
},
|
||||
|
||||
@@ -76,12 +76,15 @@ const FEATURE_FLAGS = {
|
||||
'longhornV2HugepageSettings',
|
||||
'staticIPForVM',
|
||||
'fsFreezeDeadline',
|
||||
'highPerformanceStorage',
|
||||
],
|
||||
'v1.9.1': [
|
||||
'lvmDmThinOnly',
|
||||
'highPerformanceStorage',
|
||||
],
|
||||
'v1.10.0': [
|
||||
'maintenanceModeConditions',
|
||||
'maintenanceModeDrainTimeoutSetting',
|
||||
],
|
||||
'v1.10.0': [],
|
||||
};
|
||||
|
||||
const generateFeatureFlags = () => {
|
||||
|
||||
@@ -22,7 +22,7 @@ export const HCI = {
|
||||
VM_NAME_PREFIX: 'harvesterhci.io/vmNamePrefix',
|
||||
VM_DISPLAY_NAME: 'harvesterhci.io/vmDisplayName',
|
||||
VM_RESERVED_MEMORY: 'harvesterhci.io/reservedMemory',
|
||||
MAINTENANCE_STATUS: 'harvesterhci.io/maintain-status',
|
||||
MAINTENANCE_STATUS: 'harvesterhci.io/maintain-status', // Deprecated >= v1.10.0
|
||||
HOST_CUSTOM_NAME: 'harvesterhci.io/host-custom-name',
|
||||
HOST_CONSOLE_URL: 'harvesterhci.io/host-console-url',
|
||||
RESTORE_NAME: 'restore.harvesterhci.io/name',
|
||||
|
||||
@@ -45,7 +45,8 @@ export const HCI_SETTING = {
|
||||
MAX_HOTPLUG_RATIO: 'max-hotplug-ratio',
|
||||
KUBEVIRT_MIGRATION: 'kubevirt-migration',
|
||||
INSTANCE_MANAGER_RESOURCES: 'instance-manager-resources',
|
||||
CLUSTER_POD_SECURITY_STANDARD: 'cluster-pod-security-standard'
|
||||
CLUSTER_POD_SECURITY_STANDARD: 'cluster-pod-security-standard',
|
||||
MAINTENANCE_MODE_DRAIN_TIMEOUT: 'maintenance-mode-drain-timeout'
|
||||
};
|
||||
|
||||
export const HCI_ALLOWED_SETTINGS = {
|
||||
@@ -148,6 +149,9 @@ export const HCI_ALLOWED_SETTINGS = {
|
||||
},
|
||||
[HCI_SETTING.CLUSTER_POD_SECURITY_STANDARD]: {
|
||||
kind: 'json', from: 'import', canReset: true, featureFlag: 'clusterPodSecurityStandardSetting'
|
||||
},
|
||||
[HCI_SETTING.MAINTENANCE_MODE_DRAIN_TIMEOUT]: {
|
||||
kind: 'number', canReset: true, featureFlag: 'maintenanceModeDrainTimeoutSetting'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import OverviewKeypairs from '../kubevirt.io.virtualmachine/VirtualMachineTabs/V
|
||||
import Volume from '../../edit/kubevirt.io.virtualmachine/VirtualMachineVolume';
|
||||
import Network from '../../edit/kubevirt.io.virtualmachine/VirtualMachineNetwork';
|
||||
import CloudConfig from '../../edit/kubevirt.io.virtualmachine/VirtualMachineCloudConfig';
|
||||
import { getBootDevices } from '../../utils/vm';
|
||||
const UNDEFINED = 'n/a';
|
||||
|
||||
export default {
|
||||
@@ -74,18 +75,8 @@ export default {
|
||||
return image?.spec?.displayName || '-';
|
||||
},
|
||||
|
||||
disks() {
|
||||
const disks = this?.spec?.template?.spec?.domain?.devices?.disks || [];
|
||||
|
||||
return disks.filter((disk) => {
|
||||
return !!disk.bootOrder;
|
||||
}).sort((a, b) => {
|
||||
if (a.bootOrder < b.bootOrder) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
});
|
||||
bootDevices() {
|
||||
return getBootDevices(this?.spec?.template?.spec?.domain?.devices);
|
||||
},
|
||||
|
||||
cdroms() {
|
||||
@@ -101,8 +92,10 @@ export default {
|
||||
getDeviceType(o) {
|
||||
if (o.disk) {
|
||||
return 'Disk';
|
||||
} else {
|
||||
} else if (o.cdrom) {
|
||||
return 'CD-ROM';
|
||||
} else {
|
||||
return 'Network';
|
||||
}
|
||||
},
|
||||
isEmpty(o) {
|
||||
@@ -184,10 +177,10 @@ export default {
|
||||
<div>
|
||||
<ul>
|
||||
<li
|
||||
v-for="(disk, i) in disks"
|
||||
v-for="(device, i) in bootDevices"
|
||||
:key="i"
|
||||
>
|
||||
{{ disk.bootOrder }}. {{ disk.name }} ({{ getDeviceType(disk) }})
|
||||
{{ device.bootOrder }}. {{ device.name }} ({{ getDeviceType(device) }})
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -233,6 +226,7 @@ export default {
|
||||
>
|
||||
<Network
|
||||
v-model:value="networkRows"
|
||||
:network-boot-first="networkBootFirst"
|
||||
:mode="mode"
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
@@ -15,6 +15,7 @@ import OverviewKeypairs from '../kubevirt.io.virtualmachine/VirtualMachineTabs/V
|
||||
import Volume from '../../edit/kubevirt.io.virtualmachine/VirtualMachineVolume';
|
||||
import Network from '../../edit/kubevirt.io.virtualmachine/VirtualMachineNetwork';
|
||||
import CloudConfig from '../../edit/kubevirt.io.virtualmachine/VirtualMachineCloudConfig';
|
||||
import { getBootDevices } from '../../utils/vm';
|
||||
const UNDEFINED = 'n/a';
|
||||
|
||||
export default {
|
||||
@@ -74,18 +75,8 @@ export default {
|
||||
return image?.spec?.displayName || '-';
|
||||
},
|
||||
|
||||
disks() {
|
||||
const disks = this?.spec?.template?.spec?.domain?.devices?.disks || [];
|
||||
|
||||
return disks.filter((disk) => {
|
||||
return !!disk.bootOrder;
|
||||
}).sort((a, b) => {
|
||||
if (a.bootOrder < b.bootOrder) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
});
|
||||
bootDevices() {
|
||||
return getBootDevices(this?.spec?.template?.spec?.domain?.devices);
|
||||
},
|
||||
|
||||
cdroms() {
|
||||
@@ -101,8 +92,10 @@ export default {
|
||||
getDeviceType(o) {
|
||||
if (o.disk) {
|
||||
return 'Disk';
|
||||
} else {
|
||||
} else if (o.cdrom) {
|
||||
return 'CD-ROM';
|
||||
} else {
|
||||
return 'Network';
|
||||
}
|
||||
},
|
||||
isEmpty(o) {
|
||||
@@ -184,10 +177,10 @@ export default {
|
||||
<div>
|
||||
<ul>
|
||||
<li
|
||||
v-for="(disk, i) in disks"
|
||||
v-for="(device, i) in bootDevices"
|
||||
:key="i"
|
||||
>
|
||||
{{ disk.bootOrder }}. {{ disk.name }} ({{ getDeviceType(disk) }})
|
||||
{{ device.bootOrder }}. {{ device.name }} ({{ getDeviceType(device) }})
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -234,6 +227,7 @@ export default {
|
||||
>
|
||||
<Network
|
||||
v-model:value="networkRows"
|
||||
:network-boot-first="networkBootFirst"
|
||||
:mode="mode"
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
+9
-16
@@ -6,6 +6,7 @@ import HarvesterIpAddress from '../../../formatters/HarvesterIpAddress';
|
||||
import VMConsoleBar from '../../../components/VMConsoleBar';
|
||||
import { HCI } from '../../../types';
|
||||
import { getVmCPUMemoryValues } from '../../../utils/cpuMemory';
|
||||
import { getBootDevices } from '../../../utils/vm';
|
||||
|
||||
const UNDEFINED = 'n/a';
|
||||
|
||||
@@ -69,18 +70,8 @@ export default {
|
||||
return image?.spec?.displayName || 'N/A';
|
||||
},
|
||||
|
||||
disks() {
|
||||
const disks = this.value?.spec?.template?.spec?.domain?.devices?.disks || [];
|
||||
|
||||
return disks.filter((disk) => {
|
||||
return !!disk.bootOrder;
|
||||
}).sort((a, b) => {
|
||||
if (a.bootOrder < b.bootOrder) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
});
|
||||
bootDevices() {
|
||||
return getBootDevices(this.value?.spec?.template?.spec?.domain?.devices);
|
||||
},
|
||||
|
||||
cdroms() {
|
||||
@@ -122,8 +113,10 @@ export default {
|
||||
getDeviceType(o) {
|
||||
if (o.disk) {
|
||||
return 'Disk';
|
||||
} else {
|
||||
} else if (o.cdrom) {
|
||||
return 'CD-ROM';
|
||||
} else {
|
||||
return 'Network';
|
||||
}
|
||||
},
|
||||
isEmpty(o) {
|
||||
@@ -219,16 +212,16 @@ export default {
|
||||
<div class="col span-6">
|
||||
<InputOrDisplay
|
||||
:name="t('harvester.virtualMachine.detail.details.bootOrder')"
|
||||
:value="disks"
|
||||
:value="bootDevices"
|
||||
:mode="mode"
|
||||
>
|
||||
<template #value>
|
||||
<ul>
|
||||
<li
|
||||
v-for="(disk, i) in disks"
|
||||
v-for="(device, i) in bootDevices"
|
||||
:key="i"
|
||||
>
|
||||
{{ disk.bootOrder }}. {{ disk.name }} ({{ getDeviceType(disk) }})
|
||||
{{ device.bootOrder }}. {{ device.name }} ({{ getDeviceType(device) }})
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
@@ -214,6 +214,7 @@ export default {
|
||||
|
||||
this['diskRows'] = diskRows;
|
||||
this['networkRows'] = this.getNetworkRows(neu, { fromTemplate: false, init: false });
|
||||
this['networkBootFirst'] = this.isNetworkBootFirst(neu.spec);
|
||||
|
||||
// getInitConfig() seeds these on created(); refresh them here too so the
|
||||
// VM-wide performance panel does not go stale when the spec changes.
|
||||
@@ -276,6 +277,7 @@ export default {
|
||||
>
|
||||
<Network
|
||||
v-model:value="networkRows"
|
||||
:network-boot-first="networkBootFirst"
|
||||
mode="view"
|
||||
:vm="value"
|
||||
/>
|
||||
|
||||
@@ -324,6 +324,7 @@ export default {
|
||||
>
|
||||
<Network
|
||||
v-model:value="networkRows"
|
||||
v-model:network-boot-first="networkBootFirst"
|
||||
:mode="mode"
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import InputOrDisplay from '@shell/components/InputOrDisplay';
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
import { Checkbox } from '@components/Form/Checkbox';
|
||||
|
||||
import { clone } from '@shell/utils/object';
|
||||
import { _CREATE, _VIEW } from '@shell/config/query-params';
|
||||
@@ -33,7 +34,7 @@ export default {
|
||||
emits: ['update'],
|
||||
|
||||
components: {
|
||||
LabeledInput, LabeledSelect, InputOrDisplay
|
||||
LabeledInput, LabeledSelect, InputOrDisplay, Checkbox
|
||||
},
|
||||
|
||||
props: {
|
||||
@@ -169,6 +170,7 @@ export default {
|
||||
this.value.isPod = true;
|
||||
this.value.macAddress = '';
|
||||
this.value.staticIp = '';
|
||||
this.value.networkBoot = false;
|
||||
} else {
|
||||
this.value.isPod = false;
|
||||
}
|
||||
@@ -285,6 +287,22 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!isMasquerade"
|
||||
data-testid="input-hen-networkBoot"
|
||||
class="row mb-20"
|
||||
>
|
||||
<div class="col span-12">
|
||||
<Checkbox
|
||||
v-model:value="value.networkBoot"
|
||||
:label="t('harvester.virtualMachine.network.networkBoot.label')"
|
||||
:tooltip="t('harvester.virtualMachine.network.networkBoot.tip')"
|
||||
:mode="mode"
|
||||
@update:value="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!isMasquerade && isSingle">
|
||||
<div class="row mb-20">
|
||||
<a
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import InfoBox from '@shell/components/InfoBox';
|
||||
import { RadioGroup } from '@components/Form/Radio';
|
||||
import { NETWORK_ATTACHMENT } from '@shell/config/types';
|
||||
import { sortBy } from '@shell/utils/sort';
|
||||
import { clone } from '@shell/utils/object';
|
||||
@@ -10,7 +11,11 @@ import { HCI as HCI_ANNOTATIONS } from '../../../config/labels-annotations';
|
||||
import Base from './base';
|
||||
|
||||
export default {
|
||||
components: { InfoBox, Base },
|
||||
components: {
|
||||
InfoBox, Base, RadioGroup
|
||||
},
|
||||
|
||||
emits: ['update:value', 'update:networkBootFirst'],
|
||||
|
||||
props: {
|
||||
vm: {
|
||||
@@ -35,6 +40,11 @@ export default {
|
||||
isSingle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
|
||||
networkBootFirst: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
|
||||
@@ -82,6 +92,20 @@ export default {
|
||||
|
||||
vmState() {
|
||||
return this.vm?.stateDisplay;
|
||||
},
|
||||
|
||||
hasNetworkBoot() {
|
||||
return this.rows.some((R) => R.networkBoot);
|
||||
},
|
||||
|
||||
networkBootOrderOptions() {
|
||||
return [{
|
||||
label: this.t('harvester.virtualMachine.network.networkBoot.first'),
|
||||
value: true
|
||||
}, {
|
||||
label: this.t('harvester.virtualMachine.network.networkBoot.last'),
|
||||
value: false
|
||||
}];
|
||||
}
|
||||
},
|
||||
|
||||
@@ -218,6 +242,22 @@ export default {
|
||||
/>
|
||||
</InfoBox>
|
||||
|
||||
<div
|
||||
v-if="hasNetworkBoot"
|
||||
data-testid="input-hen-networkBootFirst"
|
||||
class="mb-20"
|
||||
>
|
||||
<RadioGroup
|
||||
:value="networkBootFirst"
|
||||
name="networkBootFirst"
|
||||
:label="t('harvester.virtualMachine.network.networkBoot.order')"
|
||||
:options="networkBootOrderOptions"
|
||||
:mode="mode"
|
||||
:row="true"
|
||||
@update:value="$emit('update:networkBootFirst', $event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-if="!isView"
|
||||
type="button"
|
||||
|
||||
@@ -791,6 +791,7 @@ export default {
|
||||
>
|
||||
<Network
|
||||
v-model:value="networkRows"
|
||||
v-model:network-boot-first="networkBootFirst"
|
||||
:mode="mode"
|
||||
:is-single="isSingle"
|
||||
/>
|
||||
|
||||
@@ -286,6 +286,7 @@ harvester:
|
||||
createTemplate: Generate Template
|
||||
enableMaintenance: Enable Maintenance Mode
|
||||
disableMaintenance: Disable Maintenance Mode
|
||||
clearMaintenance: Clear Maintenance Mode Error
|
||||
enableCPUManager: Enable CPU Manager
|
||||
disableCPUManager: Disable CPU Manager
|
||||
cordon: Cordon
|
||||
@@ -871,6 +872,12 @@ harvester:
|
||||
title: Network
|
||||
addNetwork: Add Network
|
||||
addPort: Add Port
|
||||
networkBoot:
|
||||
label: Enable network boot (PXE)
|
||||
tip: Adds this interface to the VM boot order so the guest firmware can boot from the network. Requires a DHCP/TFTP (PXE) server reachable on the selected network; the management network does not provide one.
|
||||
order: Network boot order
|
||||
first: Before volumes
|
||||
last: After volumes
|
||||
cloudConfig:
|
||||
title: Cloud Configuration
|
||||
createTemplateTitle: 'Create {name}'
|
||||
@@ -2535,6 +2542,7 @@ advancedSettings:
|
||||
'harv-kubevirt-migration': 'Configure cluster-wide KubeVirt live migration parameters.'
|
||||
'harv-instance-manager-resources': 'Configure resource percentage reservations for Longhorn instance manager V1 and V2. Valid instance manager CPU range between 0 - 40.'
|
||||
'harv-cluster-pod-security-standard': 'Enforce Kubernetes Pod Security Standards (PSS) at the cluster level.'
|
||||
'harv-maintenance-mode-drain-timeout': 'Maintenance mode drain timeout in minutes. Use 0 to disable the timeout.'
|
||||
|
||||
typeLabel:
|
||||
kubevirt.io.virtualmachine: |-
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,13 @@ const ALLOW_SYSTEM_LABEL_KEYS = [
|
||||
|
||||
const HEALTHY = 'healthy';
|
||||
const WARNING = 'warning';
|
||||
const MAINTENANCE_MODE = 'MaintenanceMode';
|
||||
const MAINTENANCE_ERROR = 'Error';
|
||||
const MAINTENANCE_ENTERING_REASONS = ['Validating', 'Draining', 'Evacuating'];
|
||||
const MAINTENANCE_COMPLETED = 'Completed';
|
||||
// Legacy `harvesterhci.io/maintain-status` annotation values (clusters < v1.10)
|
||||
const LEGACY_MAINTENANCE_RUNNING = 'running';
|
||||
const LEGACY_MAINTENANCE_COMPLETED = 'completed';
|
||||
|
||||
export default class HciNode extends HarvesterResource {
|
||||
get _availableActions() {
|
||||
@@ -58,6 +65,14 @@ export default class HciNode extends HarvesterResource {
|
||||
total: 1
|
||||
};
|
||||
|
||||
const clearMaintenance = {
|
||||
action: 'clearMaintenanceMode',
|
||||
enabled: this.hasAction('clearMaintenanceMode'),
|
||||
icon: 'icon icon-fw icon-close',
|
||||
label: this.t('harvester.action.clearMaintenance'),
|
||||
total: 1
|
||||
};
|
||||
|
||||
const enableCPUManager = {
|
||||
action: 'enableCPUManager',
|
||||
enabled: this.cpuPinningFeatureEnabled && this.hasAction('enableCPUManager') && !this.isCPUManagerEnableInProgress && !this.isCPUManagerEnabled && !this.isEtcd, // witness node doesn't have CPU manager
|
||||
@@ -103,6 +118,7 @@ export default class HciNode extends HarvesterResource {
|
||||
uncordon,
|
||||
enableMaintenance,
|
||||
disableMaintenance,
|
||||
clearMaintenance,
|
||||
enableCPUManager,
|
||||
disableCPUManager,
|
||||
shutDown,
|
||||
@@ -198,6 +214,10 @@ export default class HciNode extends HarvesterResource {
|
||||
}
|
||||
|
||||
get stateDisplay() {
|
||||
if (this.isMaintenanceModeError) {
|
||||
return super.stateDisplay;
|
||||
}
|
||||
|
||||
if (this.isEnteringMaintenance) {
|
||||
return 'Entering maintenance mode';
|
||||
}
|
||||
@@ -246,6 +266,10 @@ export default class HciNode extends HarvesterResource {
|
||||
}
|
||||
|
||||
get stateDescription() {
|
||||
if (this.isMaintenanceModeError) {
|
||||
return super.stateDescription;
|
||||
}
|
||||
|
||||
const currentIP = this.metadata?.annotations?.[HCI_ANNOTATIONS.CURRENT_IP];
|
||||
const initIP = this.metadata?.annotations?.[HCI_ANNOTATIONS.INIT_IP];
|
||||
|
||||
@@ -325,7 +349,11 @@ export default class HciNode extends HarvesterResource {
|
||||
}
|
||||
|
||||
disableMaintenanceMode() {
|
||||
this.doAction('disableMaintenanceMode', {});
|
||||
this.doActionGrowl('disableMaintenanceMode', {});
|
||||
}
|
||||
|
||||
clearMaintenanceMode() {
|
||||
this.doActionGrowl('clearMaintenanceMode', {});
|
||||
}
|
||||
|
||||
enableCPUManager() {
|
||||
@@ -343,11 +371,32 @@ export default class HciNode extends HarvesterResource {
|
||||
);
|
||||
}
|
||||
|
||||
get maintenanceModeCondition() {
|
||||
return (this.status?.conditions || []).find(
|
||||
(c) => c.type === MAINTENANCE_MODE
|
||||
);
|
||||
}
|
||||
|
||||
get maintenanceModeConditionsEnabled() {
|
||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('maintenanceModeConditions');
|
||||
}
|
||||
|
||||
// Clusters < v1.10 only expose the `harvesterhci.io/maintain-status` annotation
|
||||
get legacyMaintenanceStatus() {
|
||||
return this.metadata?.annotations?.[HCI_ANNOTATIONS.MAINTENANCE_STATUS];
|
||||
}
|
||||
|
||||
get isMaintenanceModeError() {
|
||||
return this.maintenanceModeConditionsEnabled && this.maintenanceModeCondition?.reason === MAINTENANCE_ERROR;
|
||||
}
|
||||
|
||||
get isMigratable() {
|
||||
const states = ['in-progress', 'unavailable'];
|
||||
|
||||
const engaged = this.maintenanceModeConditionsEnabled ? this.maintenanceModeCondition?.status === 'True' : !!this.legacyMaintenanceStatus;
|
||||
|
||||
return (
|
||||
!this.metadata?.annotations?.[HCI_ANNOTATIONS.MAINTENANCE_STATUS] &&
|
||||
!engaged &&
|
||||
!this.isUnSchedulable &&
|
||||
!states.includes(this.state)
|
||||
);
|
||||
@@ -362,17 +411,26 @@ export default class HciNode extends HarvesterResource {
|
||||
}
|
||||
|
||||
get isEnteringMaintenance() {
|
||||
if (!this.maintenanceModeConditionsEnabled) {
|
||||
return this.legacyMaintenanceStatus === LEGACY_MAINTENANCE_RUNNING;
|
||||
}
|
||||
|
||||
const cond = this.maintenanceModeCondition;
|
||||
|
||||
return (
|
||||
this.metadata?.annotations?.[HCI_ANNOTATIONS.MAINTENANCE_STATUS] ===
|
||||
'running'
|
||||
cond?.status === 'True' &&
|
||||
MAINTENANCE_ENTERING_REASONS.includes(cond?.reason)
|
||||
);
|
||||
}
|
||||
|
||||
get isMaintenance() {
|
||||
return (
|
||||
this.metadata?.annotations?.[HCI_ANNOTATIONS.MAINTENANCE_STATUS] ===
|
||||
'completed'
|
||||
);
|
||||
if (!this.maintenanceModeConditionsEnabled) {
|
||||
return this.legacyMaintenanceStatus === LEGACY_MAINTENANCE_COMPLETED;
|
||||
}
|
||||
|
||||
const cond = this.maintenanceModeCondition;
|
||||
|
||||
return cond?.status === 'True' && cond?.reason === MAINTENANCE_COMPLETED;
|
||||
}
|
||||
|
||||
get cpuPinningFeatureEnabled() {
|
||||
|
||||
@@ -11,3 +11,12 @@ export function parseVolumeClaimTemplates(data) {
|
||||
}
|
||||
|
||||
export const EMPTY_IMAGE = 'EMPTY_IMAGE';
|
||||
|
||||
/**
|
||||
* Disks and interfaces that take part in the boot sequence, sorted by boot order.
|
||||
*/
|
||||
export function getBootDevices(devices = {}) {
|
||||
return [...(devices.disks || []), ...(devices.interfaces || [])]
|
||||
.filter((device) => !!device.bootOrder)
|
||||
.sort((a, b) => a.bootOrder - b.bootOrder);
|
||||
}
|
||||
|
||||
@@ -3099,11 +3099,16 @@
|
||||
"@typescript-eslint/types" "8.62.0"
|
||||
"@typescript-eslint/visitor-keys" "8.62.0"
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@8.62.0", "@typescript-eslint/tsconfig-utils@^8.62.0":
|
||||
"@typescript-eslint/tsconfig-utils@8.62.0":
|
||||
version "8.62.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.0.tgz#9440a673581c6d9de308c4d5803dd52ed5d71729"
|
||||
integrity sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@^8.62.0":
|
||||
version "8.68.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.68.0.tgz#594d7a3c5952055b3c431fc563ca7fd1defcce18"
|
||||
integrity sha512-F7zrGQfiJHojPwi8vhxZQC1tWtJzvL74cK/nqri2lk8YUXvYaYwl263xOJ69jDWPUk1hmcdoayFwk9lX09npVw==
|
||||
|
||||
"@typescript-eslint/type-utils@8.62.0":
|
||||
version "8.62.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.62.0.tgz#6f64d813ed9f340d796baed40cdab86b8e9a491a"
|
||||
@@ -3115,11 +3120,16 @@
|
||||
debug "^4.4.3"
|
||||
ts-api-utils "^2.5.0"
|
||||
|
||||
"@typescript-eslint/types@8.62.0", "@typescript-eslint/types@^8.62.0":
|
||||
"@typescript-eslint/types@8.62.0":
|
||||
version "8.62.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.62.0.tgz#601427c10203d9f0f34f0b3e474df735eb12b593"
|
||||
integrity sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==
|
||||
|
||||
"@typescript-eslint/types@^8.62.0":
|
||||
version "8.68.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.68.0.tgz#3f9d4e62fbe5728f09403cdc7b4d58af842ac1af"
|
||||
integrity sha512-9RnpsGJjrAllCMefGVVsImJM24YurhC0Q1h4UbvivtvOqXmR/vEJge2OoE++z9m6hyg8T1Q8t5SNT6tHSbrxcg==
|
||||
|
||||
"@typescript-eslint/typescript-estree@8.62.0":
|
||||
version "8.62.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.0.tgz#b96b55d02e26aa09434421c3fa678e525ca09a4c"
|
||||
@@ -6116,10 +6126,10 @@ element-matches@^0.1.2:
|
||||
resolved "https://registry.yarnpkg.com/element-matches/-/element-matches-0.1.2.tgz#7345cb71e965bd2b12f725e524591c102198361a"
|
||||
integrity sha512-yWh1otcs3OKUWDvu/IxyI36ZI3WNaRZlI0uG/DK6fu0pap0VYZ0J5pEGTk1zakme+hT0OKHwhlHc0N5TJhY6yQ==
|
||||
|
||||
elkjs@^0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/elkjs/-/elkjs-0.12.0.tgz#7dc1bc71ab8f402d1b6564e2fa509ca1caee276c"
|
||||
integrity sha512-YZcKynxVxYoKIOEpywEPwCFdg+BTbxQRNf3pbwdDCvc8O3kQD8bmIwSxKU1eOTVc4Xo+VG9Te+575mlfvOrhEQ==
|
||||
elkjs@^0.11.0:
|
||||
version "0.11.1"
|
||||
resolved "https://registry.yarnpkg.com/elkjs/-/elkjs-0.11.1.tgz#d27fcdbbf5a8aeeff80420d17d1666f78cfc8544"
|
||||
integrity sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==
|
||||
|
||||
elliptic@^6.5.3, elliptic@^6.6.1:
|
||||
version "6.6.1"
|
||||
|
||||
Reference in New Issue
Block a user