mirror of https://github.com/rancher/dashboard.git
920 lines
22 KiB
JavaScript
920 lines
22 KiB
JavaScript
import Vue from 'vue';
|
|
import { load } from 'js-yaml';
|
|
import { colorForState } from '@shell/plugins/dashboard-store/resource-class';
|
|
import { POD, NODE, HCI, PVC } from '@shell/config/types';
|
|
import { findBy } from '@shell/utils/array';
|
|
import { parseSi } from '@shell/utils/units';
|
|
import { get } from '@shell/utils/object';
|
|
import { HCI as HCI_ANNOTATIONS } from '@shell/config/labels-annotations';
|
|
import { _CLONE } from '@shell/config/query-params';
|
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
import HarvesterResource from '~/pkg/harvester/models/harvester';
|
|
|
|
export const OFF = 'Off';
|
|
|
|
const VMI_WAITING_MESSAGE =
|
|
'The virtual machine is waiting for resources to become available.';
|
|
const VM_ERROR = 'VM error';
|
|
const STOPPING = 'Stopping';
|
|
const WAITING = 'Waiting';
|
|
const NOT_READY = 'Not Ready';
|
|
const AGENT_CONNECTED = 'AgentConnected';
|
|
|
|
const PAUSED = 'Paused';
|
|
const PAUSED_VM_MODAL_MESSAGE =
|
|
'This VM has been paused. If you wish to unpause it, please click the Unpause button below. For further details, please check with your system administrator.';
|
|
|
|
const POD_STATUS_NOT_SCHEDULABLE = 'POD_NOT_SCHEDULABLE';
|
|
const POD_STATUS_CONTAINER_FAILING = 'POD_CONTAINER_FAILING';
|
|
// eslint-disable-next-line no-unused-vars
|
|
const POD_STATUS_NOT_READY = 'POD_NOT_READY';
|
|
|
|
const POD_STATUS_FAILED = 'POD_FAILED';
|
|
const POD_STATUS_CRASHLOOP_BACKOFF = 'POD_CRASHLOOP_BACKOFF';
|
|
const POD_STATUS_UNKNOWN = 'POD_STATUS_UNKNOWN';
|
|
|
|
const POD_STATUS_ALL_ERROR = [
|
|
POD_STATUS_NOT_SCHEDULABLE,
|
|
POD_STATUS_CONTAINER_FAILING,
|
|
POD_STATUS_FAILED,
|
|
POD_STATUS_CRASHLOOP_BACKOFF,
|
|
POD_STATUS_UNKNOWN
|
|
];
|
|
|
|
const POD_STATUS_COMPLETED = 'POD_STATUS_COMPLETED';
|
|
const POD_STATUS_SUCCEEDED = 'POD_STATUS_SUCCEEDED';
|
|
const POD_STATUS_RUNNING = 'POD_STATUS_RUNNING';
|
|
|
|
const POD_STATUS_ALL_READY = [
|
|
POD_STATUS_RUNNING,
|
|
POD_STATUS_COMPLETED,
|
|
POD_STATUS_SUCCEEDED
|
|
];
|
|
|
|
const RunStrategy = {
|
|
Always: 'Always',
|
|
RerunOnFailure: 'RerunOnFailure',
|
|
Halted: 'Halted',
|
|
Manual: 'Manual'
|
|
};
|
|
|
|
const StateChangeRequest = {
|
|
Start: 'Start',
|
|
Stop: 'Stop'
|
|
};
|
|
|
|
const STARTING_MESSAGE =
|
|
'This virtual machine will start shortly. Preparing storage, networking, and compute resources.';
|
|
|
|
const VMIPhase = {
|
|
Pending: 'Pending',
|
|
Scheduling: 'Scheduling',
|
|
Scheduled: 'Scheduled',
|
|
Running: 'Running',
|
|
Succeeded: 'Succeeded',
|
|
Failed: 'Failed',
|
|
Unknown: 'Unknown'
|
|
};
|
|
|
|
const IgnoreMessages = ['pod has unbound immediate PersistentVolumeClaims'];
|
|
|
|
export default class VirtVm extends HarvesterResource {
|
|
get availableActions() {
|
|
const out = super._availableActions;
|
|
|
|
return [
|
|
{
|
|
action: 'stopVM',
|
|
enabled: !!this.actions?.stop,
|
|
icon: 'icon icon-close',
|
|
label: this.t('harvester.action.stop'),
|
|
bulkable: true
|
|
},
|
|
{
|
|
action: 'pauseVM',
|
|
enabled: !!this.actions?.pause,
|
|
icon: 'icon icon-pause',
|
|
label: this.t('harvester.action.pause')
|
|
},
|
|
{
|
|
action: 'unpauseVM',
|
|
enabled: !!this.actions?.unpause,
|
|
icon: 'icon icon-spinner',
|
|
label: this.t('harvester.action.unpause')
|
|
},
|
|
{
|
|
action: 'restartVM',
|
|
enabled: !!this.actions?.restart,
|
|
icon: 'icon icon-refresh',
|
|
label: this.t('harvester.action.restart'),
|
|
bulkable: true
|
|
},
|
|
{
|
|
action: 'softrebootVM',
|
|
enabled: !!this.actions?.softreboot,
|
|
icon: 'icon icon-refresh',
|
|
label: this.t('harvester.action.softreboot')
|
|
},
|
|
{
|
|
action: 'startVM',
|
|
enabled: !!this.actions?.start,
|
|
icon: 'icon icon-play',
|
|
label: this.t('harvester.action.start'),
|
|
bulkable: true
|
|
},
|
|
{
|
|
action: 'backupVM',
|
|
enabled: !!this.actions?.backup,
|
|
icon: 'icon icon-backup',
|
|
label: this.t('harvester.action.backup')
|
|
},
|
|
{
|
|
action: 'restoreVM',
|
|
enabled: !!this.actions?.restore,
|
|
icon: 'icon icon-backup-restore',
|
|
label: this.t('harvester.action.restore')
|
|
},
|
|
{
|
|
action: 'ejectCDROM',
|
|
enabled: !!this.actions?.ejectCdRom,
|
|
icon: 'icon icon-delete',
|
|
label: this.t('harvester.action.ejectCDROM')
|
|
},
|
|
{
|
|
action: 'migrateVM',
|
|
enabled: !!this.actions?.migrate,
|
|
icon: 'icon icon-copy',
|
|
label: this.t('harvester.action.migrate')
|
|
},
|
|
{
|
|
action: 'abortMigrationVM',
|
|
enabled: !!this.actions?.abortMigration,
|
|
icon: 'icon icon-close',
|
|
label: this.t('harvester.action.abortMigration')
|
|
},
|
|
{
|
|
action: 'addHotplug',
|
|
enabled: !!this.actions?.addVolume,
|
|
icon: 'icon icon-plus',
|
|
label: this.t('harvester.action.addHotplug')
|
|
},
|
|
{
|
|
action: 'createTemplate',
|
|
enabled: !!this.actions?.createTemplate,
|
|
icon: 'icon icon-copy',
|
|
label: this.t('harvester.action.createTemplate')
|
|
},
|
|
{
|
|
action: 'openLogs',
|
|
enabled: !!this.links.view,
|
|
icon: 'icon icon-fw icon-chevron-right',
|
|
label: this.t('harvester.action.viewlogs'),
|
|
total: 1
|
|
},
|
|
...out
|
|
];
|
|
}
|
|
|
|
applyDefaults(resources = this, realMode) {
|
|
const spec = {
|
|
runStrategy: 'RerunOnFailure',
|
|
template: {
|
|
metadata: { annotations: {} },
|
|
spec: {
|
|
domain: {
|
|
machine: { type: '' },
|
|
cpu: {
|
|
cores: null,
|
|
sockets: 1,
|
|
threads: 1
|
|
},
|
|
devices: {
|
|
inputs: [
|
|
{
|
|
bus: 'usb',
|
|
name: 'tablet',
|
|
type: 'tablet'
|
|
}
|
|
],
|
|
interfaces: [
|
|
{
|
|
masquerade: {},
|
|
model: 'virtio',
|
|
name: 'default'
|
|
}
|
|
],
|
|
disks: []
|
|
},
|
|
resources: {
|
|
limits: {
|
|
memory: null,
|
|
cpu: ''
|
|
}
|
|
},
|
|
features: { acpi: { enabled: true } }
|
|
},
|
|
evictionStrategy: 'LiveMigrate',
|
|
hostname: '',
|
|
networks: [
|
|
{
|
|
name: 'default',
|
|
pod: {}
|
|
}
|
|
],
|
|
volumes: []
|
|
}
|
|
}
|
|
};
|
|
|
|
if (realMode !== _CLONE) {
|
|
Vue.set(this.metadata, 'annotations', { [HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE]: '[]' });
|
|
Vue.set(this, 'spec', spec);
|
|
}
|
|
}
|
|
|
|
cleanForNew() {
|
|
this.$dispatch(`cleanForNew`, this);
|
|
|
|
this.spec.template.spec.hostname = '';
|
|
const interfaces = this.spec.template.spec.domain.devices?.interfaces || [];
|
|
|
|
for (let i = 0; i < interfaces.length; i++) {
|
|
if (interfaces[i].macAddress) {
|
|
interfaces[i].macAddress = '';
|
|
}
|
|
}
|
|
}
|
|
|
|
restartVM() {
|
|
this.doActionGrowl('restart', {});
|
|
}
|
|
|
|
softrebootVM() {
|
|
this.doActionGrowl('softreboot', {});
|
|
}
|
|
|
|
openLogs() {
|
|
this.$dispatch(
|
|
'wm/open',
|
|
{
|
|
id: `${ this.id }-logs`,
|
|
label: this.nameDisplay,
|
|
icon: 'file',
|
|
component: 'ContainerLogs',
|
|
attrs: { pod: this.podResource }
|
|
},
|
|
{ root: true }
|
|
);
|
|
}
|
|
|
|
backupVM(resources = this) {
|
|
this.$dispatch('promptModal', {
|
|
resources,
|
|
component: 'HarvesterBackupModal'
|
|
});
|
|
}
|
|
|
|
unplugVolume(diskName) {
|
|
const resources = this;
|
|
|
|
this.$dispatch('promptModal', {
|
|
resources,
|
|
diskName,
|
|
component: 'HarvesterUnplugVolume'
|
|
});
|
|
}
|
|
|
|
restoreVM(resources = this) {
|
|
this.$dispatch('promptModal', {
|
|
resources,
|
|
component: 'HarvesterRestoreDialog'
|
|
});
|
|
}
|
|
|
|
get machineType() {
|
|
return this.spec?.template?.spec?.domain?.machine?.type || '';
|
|
}
|
|
|
|
get realAttachNodeName() {
|
|
const vmi = this.$getters['byId'](HCI.VMI, this.id);
|
|
const nodeName = vmi?.status?.nodeName;
|
|
const node = this.$getters['byId'](NODE, nodeName);
|
|
|
|
return node?.nameDisplay || '';
|
|
}
|
|
|
|
get nodeName() {
|
|
const vmi = this.$getters['byId'](HCI.VMI, this.id);
|
|
const nodeName = vmi?.status?.nodeName;
|
|
const node = this.$getters['byId'](NODE, nodeName);
|
|
|
|
return node?.id;
|
|
}
|
|
|
|
pauseVM() {
|
|
this.doActionGrowl('pause', {});
|
|
}
|
|
|
|
unpauseVM() {
|
|
this.doActionGrowl('unpause', {});
|
|
}
|
|
|
|
stopVM() {
|
|
this.doActionGrowl('stop', {});
|
|
}
|
|
|
|
startVM() {
|
|
this.doActionGrowl('start', {});
|
|
}
|
|
|
|
migrateVM(resources = this) {
|
|
this.$dispatch('promptModal', {
|
|
resources,
|
|
component: 'HarvesterMigrationDialog'
|
|
});
|
|
}
|
|
|
|
ejectCDROM(resources = this) {
|
|
this.$dispatch('promptModal', {
|
|
resources,
|
|
component: 'HarvesterEjectCDROMDialog'
|
|
});
|
|
}
|
|
|
|
abortMigrationVM() {
|
|
this.doActionGrowl('abortMigration', {});
|
|
}
|
|
|
|
createTemplate(resources = this) {
|
|
this.$dispatch('promptModal', {
|
|
resources,
|
|
component: 'HarvesterCloneTemplate'
|
|
});
|
|
}
|
|
|
|
addHotplug(resources = this) {
|
|
this.$dispatch('promptModal', {
|
|
resources,
|
|
component: 'HarvesterAddHotplugModal'
|
|
});
|
|
}
|
|
|
|
get networksName() {
|
|
const interfaces = this.spec.template.spec.domain.devices?.interfaces || [];
|
|
|
|
return interfaces.map(I => I.name);
|
|
}
|
|
|
|
get isOff() {
|
|
return !this.isVMExpectedRunning ? { status: OFF } : null;
|
|
}
|
|
|
|
get isWaitingForVMI() {
|
|
if (this && this.isVMExpectedRunning && !this.isVMCreated) {
|
|
return { status: WAITING, message: VMI_WAITING_MESSAGE };
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
get isVMExpectedRunning() {
|
|
if (!this?.spec) {
|
|
return false;
|
|
}
|
|
const { running = null, runStrategy = null } = this.spec;
|
|
const conditions = this?.status?.conditions || [];
|
|
|
|
if (running) {
|
|
return true;
|
|
}
|
|
|
|
if (runStrategy !== null) {
|
|
let changeRequests;
|
|
|
|
switch (runStrategy) {
|
|
case RunStrategy.Halted:
|
|
return false;
|
|
case RunStrategy.Always:
|
|
return true;
|
|
case RunStrategy.RerunOnFailure:
|
|
if (
|
|
this.status?.printableStatus === 'ErrorUnschedulable' &&
|
|
conditions.find(
|
|
C => C.message && C.message.includes(IgnoreMessages)
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return ['Starting', 'Running'].includes(this.status?.printableStatus);
|
|
case RunStrategy.Manual:
|
|
default:
|
|
changeRequests = new Set(
|
|
(this.status?.stateChangeRequests || []).map(
|
|
chRequest => chRequest?.action
|
|
)
|
|
);
|
|
|
|
if (changeRequests.has(StateChangeRequest.Stop)) {
|
|
return false;
|
|
}
|
|
if (changeRequests.has(StateChangeRequest.Start)) {
|
|
return true;
|
|
}
|
|
|
|
if (changeRequests.size === 0) {
|
|
return ['Starting', 'Running'].includes(
|
|
this.status?.printableStatus
|
|
);
|
|
}
|
|
|
|
return this.isVMCreated; // if there is no change request we can assume created is representing running (current and expected)
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
get podResource() {
|
|
const vmiResource = this.$rootGetters['harvester/byId'](HCI.VMI, this.id);
|
|
const podList = this.$rootGetters['harvester/all'](POD);
|
|
|
|
return podList.find((P) => {
|
|
return (
|
|
vmiResource?.metadata?.name &&
|
|
vmiResource?.metadata?.name === P.metadata?.ownerReferences?.[0].name
|
|
);
|
|
});
|
|
}
|
|
|
|
get isPaused() {
|
|
const conditions = this.vmi?.status?.conditions || [];
|
|
const isPause = conditions.filter(cond => cond.type === PAUSED).length > 0;
|
|
|
|
return isPause ? {
|
|
status: PAUSED,
|
|
message: PAUSED_VM_MODAL_MESSAGE
|
|
} : null;
|
|
}
|
|
|
|
get isVMError() {
|
|
const conditions = get(this, 'status.conditions');
|
|
const vmFailureCond = findBy(conditions, 'type', 'Failure');
|
|
|
|
if (vmFailureCond) {
|
|
return {
|
|
status: VM_ERROR,
|
|
detailedMessage: vmFailureCond.message
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
get vmi() {
|
|
const vmis = this.$rootGetters['harvester/all'](HCI.VMI);
|
|
|
|
return vmis.find(VMI => VMI.id === this.id);
|
|
}
|
|
|
|
get isError() {
|
|
const conditions = get(this.vmi, 'status.conditions');
|
|
const vmiFailureCond = findBy(conditions, 'type', 'Failure');
|
|
|
|
if (vmiFailureCond) {
|
|
return { status: 'VMI error', detailedMessage: vmiFailureCond.message };
|
|
}
|
|
|
|
if ((this.vmi || this.isVMCreated) && this.podResource) {
|
|
// const podStatus = this.podResource.getPodStatus;
|
|
// if (POD_STATUS_ALL_ERROR.includes(podStatus?.status)) {
|
|
// return {
|
|
// ...podStatus,
|
|
// status: 'LAUNCHER_POD_ERROR',
|
|
// pod: this.podResource,
|
|
// };
|
|
// }
|
|
}
|
|
|
|
return this?.vmi?.status?.phase;
|
|
}
|
|
|
|
get isRunning() {
|
|
const conditions = get(this.vmi, 'status.conditions');
|
|
const isVMIReady = findBy(conditions, 'type', 'Ready')?.status === 'True';
|
|
|
|
if (this.vmi?.status?.phase === VMIPhase.Running && isVMIReady) {
|
|
return { status: VMIPhase.Running };
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
get isNotReady() {
|
|
const conditions = get(this.vmi, 'status.conditions');
|
|
const VMIReadyCondition = findBy(conditions, 'type', 'Ready');
|
|
|
|
if (
|
|
VMIReadyCondition?.status === 'False' &&
|
|
this.vmi?.status?.phase === VMIPhase.Running
|
|
) {
|
|
return { status: NOT_READY };
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
get isBeingStopped() {
|
|
if (this && !this.isVMExpectedRunning && this.isVMCreated && this.vmi?.status?.phase !== VMIPhase.Succeeded) {
|
|
return { status: STOPPING };
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
get isStarting() {
|
|
if (this.isVMExpectedRunning && this.isVMCreated) {
|
|
// created but not yet ready
|
|
if (this.podResource) {
|
|
const podStatus = this.podResource.getPodStatus;
|
|
|
|
if (!POD_STATUS_ALL_READY.includes(podStatus?.status)) {
|
|
return {
|
|
...podStatus,
|
|
status: 'Starting',
|
|
message: STARTING_MESSAGE,
|
|
detailedMessage: podStatus?.message,
|
|
pod: this.podResource
|
|
};
|
|
}
|
|
}
|
|
|
|
return {
|
|
status: 'Starting',
|
|
message: STARTING_MESSAGE,
|
|
pod: this.podResource
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
get otherState() {
|
|
const state = (this.vmi &&
|
|
[VMIPhase.Scheduling, VMIPhase.Scheduled].includes(
|
|
this.vmi?.status?.phase
|
|
) && {
|
|
status: 'Starting',
|
|
message: STARTING_MESSAGE
|
|
}) ||
|
|
(this.vmi &&
|
|
this.vmi.status?.phase === VMIPhase.Pending && {
|
|
status: 'VMI_WAITING',
|
|
message: VMI_WAITING_MESSAGE
|
|
}) ||
|
|
(this.vmi &&
|
|
this.vmi?.status?.phase === VMIPhase.Failed && { status: 'VMI_ERROR' }) ||
|
|
(this.isVMExpectedRunning &&
|
|
!this.isVMCreated && { status: 'Pending' }) || { status: 'UNKNOWN' };
|
|
|
|
return state;
|
|
}
|
|
|
|
get isVMCreated() {
|
|
return !!this?.status?.created;
|
|
}
|
|
|
|
get getDataVolumeTemplates() {
|
|
return get(this, 'spec.volumeClaimTemplates') === null ? [] : this.spec.volumeClaimTemplates;
|
|
}
|
|
|
|
restoreState(vmResource = this, id) {
|
|
if (!id) {
|
|
id = `${ this.metadata.namespace }/${ get(
|
|
vmResource,
|
|
`metadata.annotations."${ HCI_ANNOTATIONS.RESTORE_NAME }"`
|
|
) }`;
|
|
}
|
|
const allRestore = this.$rootGetters['harvester/all'](HCI.RESTORE);
|
|
|
|
const restoreResource = allRestore.find(O => O.id === id);
|
|
|
|
if (!restoreResource) {
|
|
return true;
|
|
}
|
|
|
|
return restoreResource?.isComplete;
|
|
}
|
|
|
|
get actualState() {
|
|
if (!this.restoreState()) {
|
|
return 'Restoring';
|
|
}
|
|
|
|
if (this?.metadata?.deletionTimestamp) {
|
|
return 'Terminating';
|
|
}
|
|
|
|
if (
|
|
!!this?.vmi?.migrationState &&
|
|
this.vmi.migrationState.status !== 'Failed'
|
|
) {
|
|
return this.vmi.migrationState.status;
|
|
}
|
|
|
|
const state =
|
|
this.isPaused?.status ||
|
|
this.isVMError?.status ||
|
|
this.isBeingStopped?.status ||
|
|
this.isOff?.status ||
|
|
this.isError?.status ||
|
|
this.isRunning?.status ||
|
|
this.isNotReady?.status ||
|
|
this.isStarting?.status ||
|
|
this.isWaitingForVMI?.state ||
|
|
this.otherState?.status;
|
|
|
|
return state;
|
|
}
|
|
|
|
get warningMessage() {
|
|
const conditions = get(this, 'status.conditions');
|
|
const vmFailureCond = findBy(conditions, 'type', 'Failure');
|
|
|
|
if (vmFailureCond) {
|
|
return {
|
|
status: VM_ERROR,
|
|
message: vmFailureCond.message
|
|
};
|
|
}
|
|
|
|
const vmiConditions = get(this.vmi, 'status.conditions');
|
|
const vmiFailureCond = findBy(vmiConditions, 'type', 'Failure');
|
|
|
|
if (vmiFailureCond) {
|
|
return { status: 'VMI error', detailedMessage: vmiFailureCond.message };
|
|
}
|
|
|
|
if ((this.vmi || this.isVMCreated) && this.podResource) {
|
|
const podStatus = this.podResource.getPodStatus;
|
|
|
|
if (POD_STATUS_ALL_ERROR.includes(podStatus?.status)) {
|
|
return {
|
|
...podStatus,
|
|
status: 'LAUNCHER_POD_ERROR',
|
|
pod: this.podResource
|
|
};
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
get migrationMessage() {
|
|
if (
|
|
!!this?.vmi?.migrationState &&
|
|
this.vmi.migrationState.status === 'Failed'
|
|
) {
|
|
return {
|
|
...this.actualState,
|
|
message: this.t('harvester.modal.migration.failedMessage')
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
get stateDisplay() {
|
|
return this.actualState;
|
|
}
|
|
|
|
get stateColor() {
|
|
const state = this.actualState;
|
|
|
|
return colorForState(state);
|
|
}
|
|
|
|
get networkIps() {
|
|
let networkData = '';
|
|
const out = [];
|
|
const arrVolumes = this.spec.template?.spec?.volumes || [];
|
|
|
|
arrVolumes.forEach((V) => {
|
|
if (V.cloudInitNoCloud) {
|
|
networkData = V.cloudInitNoCloud.networkData;
|
|
}
|
|
});
|
|
|
|
try {
|
|
const newInitScript = load(networkData);
|
|
|
|
if (newInitScript?.config && Array.isArray(newInitScript.config)) {
|
|
const config = newInitScript.config;
|
|
|
|
config.forEach((O) => {
|
|
if (O?.subnets && Array.isArray(O.subnets)) {
|
|
const subnets = O.subnets;
|
|
|
|
subnets.forEach((S) => {
|
|
if (S.address) {
|
|
out.push(S.address);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
} catch (err) {}
|
|
|
|
return out;
|
|
}
|
|
|
|
get warningCount() {
|
|
return this.resourcesStatus.warningCount;
|
|
}
|
|
|
|
get errorCount() {
|
|
return this.resourcesStatus.errorCount;
|
|
}
|
|
|
|
get resourcesStatus() {
|
|
const vmList = this.$rootGetters['harvester/all'](HCI.VM);
|
|
let warningCount = 0;
|
|
let errorCount = 0;
|
|
|
|
vmList.forEach((vm) => {
|
|
const status = vm.actualState;
|
|
|
|
if (status === VM_ERROR) {
|
|
errorCount += 1;
|
|
} else if (
|
|
status === 'Stopping' ||
|
|
status === 'Waiting' ||
|
|
status === 'Pending' ||
|
|
status === 'Starting' ||
|
|
status === 'Terminating'
|
|
) {
|
|
warningCount += 1;
|
|
}
|
|
});
|
|
|
|
return {
|
|
warningCount,
|
|
errorCount
|
|
};
|
|
}
|
|
|
|
get volumeClaimTemplates() {
|
|
let out = [];
|
|
|
|
try {
|
|
out = JSON.parse(
|
|
this.metadata?.annotations?.[HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE]
|
|
);
|
|
} catch (e) {}
|
|
|
|
return out;
|
|
}
|
|
|
|
get persistentVolumeClaimName() {
|
|
const volumes = this.spec.template.spec.volumes || [];
|
|
|
|
return volumes
|
|
.map((O) => {
|
|
return O?.persistentVolumeClaim?.claimName;
|
|
})
|
|
.filter(name => !!name);
|
|
}
|
|
|
|
get rootImageId() {
|
|
let imageId = '';
|
|
const pvcs = this.$rootGetters[`harvester/all`](PVC) || [];
|
|
|
|
const volumes = this.spec.template.spec.volumes || [];
|
|
|
|
const firstVolumeName = volumes[0]?.persistentVolumeClaim?.claimName;
|
|
const isNoExistingVolume = this.volumeClaimTemplates.find((volume) => {
|
|
return firstVolumeName === volume?.metadata?.name;
|
|
});
|
|
|
|
if (!isNoExistingVolume) {
|
|
const existingVolume = pvcs.find(
|
|
P => P.id === `${ this.metadata.namespace }/${ firstVolumeName }`
|
|
);
|
|
|
|
if (existingVolume) {
|
|
return existingVolume?.metadata?.annotations?.[
|
|
'harvesterhci.io/imageId'
|
|
];
|
|
}
|
|
}
|
|
|
|
this.volumeClaimTemplates.find((volume) => {
|
|
imageId = volume?.metadata?.annotations?.['harvesterhci.io/imageId'];
|
|
|
|
return !!imageId;
|
|
});
|
|
|
|
return imageId;
|
|
}
|
|
|
|
get restoreName() {
|
|
return (
|
|
get(this, `metadata.annotations."${ HCI_ANNOTATIONS.RESTORE_NAME }"`) || ''
|
|
);
|
|
}
|
|
|
|
get customValidationRules() {
|
|
const rules = [
|
|
{
|
|
nullable: false,
|
|
path: 'metadata.name',
|
|
required: true,
|
|
minLength: 1,
|
|
maxLength: 63,
|
|
translationKey: 'harvester.fields.name'
|
|
},
|
|
{
|
|
nullable: false,
|
|
path: 'spec.template.spec.domain.cpu.cores',
|
|
min: 1,
|
|
required: true,
|
|
translationKey: 'harvester.fields.cpu'
|
|
},
|
|
{
|
|
nullable: false,
|
|
path: 'spec.template.spec.domain.resources.limits.memory',
|
|
required: true,
|
|
translationKey: 'harvester.fields.memory'
|
|
},
|
|
{
|
|
nullable: false,
|
|
path: 'spec.template.spec',
|
|
validators: ['vmNetworks']
|
|
},
|
|
{
|
|
nullable: false,
|
|
path: 'spec',
|
|
validators: [`vmDisks`]
|
|
}
|
|
];
|
|
|
|
return rules;
|
|
}
|
|
|
|
get attachNetwork() {
|
|
const networks = this.spec?.template?.spec?.networks || [];
|
|
const hasMultus = networks.find(N => N.multus);
|
|
|
|
return !!hasMultus;
|
|
}
|
|
|
|
get memorySort() {
|
|
const memory =
|
|
this?.spec?.template?.spec?.domain?.resources?.requests?.memory || 0;
|
|
|
|
const formatSize = parseSi(memory);
|
|
|
|
return parseInt(formatSize);
|
|
}
|
|
|
|
get ingoreVMMessage() {
|
|
const ignoreConditions = [
|
|
{
|
|
name: 'unavailable',
|
|
error: false,
|
|
vmState: this.actualState === PAUSED
|
|
}
|
|
];
|
|
|
|
const state = this.metadata?.state;
|
|
|
|
return (
|
|
ignoreConditions.find(
|
|
condition => condition.name === state?.name &&
|
|
condition.error === state?.error &&
|
|
condition.vmState
|
|
) ||
|
|
IgnoreMessages.find(M => super.stateDescription?.includes(M)) ||
|
|
this.isOff
|
|
);
|
|
}
|
|
|
|
get stateDescription() {
|
|
return this.ingoreVMMessage ? '' : super.stateDescription;
|
|
}
|
|
|
|
get displayMemory() {
|
|
return (
|
|
this.spec.template.spec.domain.resources?.limits?.memory ||
|
|
this.spec.template.spec.domain.resources?.requests?.memory
|
|
);
|
|
}
|
|
|
|
get isQemuInstalled() {
|
|
const conditions = this.vmi?.status?.conditions || [];
|
|
const qemu = conditions.find(cond => cond.type === AGENT_CONNECTED);
|
|
|
|
return qemu?.status === 'True';
|
|
}
|
|
}
|