Latest changes from Harvester master - d7d9d4af8a88d677695d7aff47a81d52041dfcca

Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
This commit is contained in:
Francesco Torchia
2024-11-12 11:50:17 +01:00
parent d04cd35a08
commit dc74441d26
46 changed files with 1290 additions and 1624 deletions
+1 -1
View File
@@ -489,7 +489,7 @@ export default class HciNode extends HarvesterResource {
get unProvisionedDisks() {
const blockDevices = this.blockDevices || [];
return blockDevices.filter(d => d?.spec?.fileSystem?.provisioned && d?.status?.provisionPhase !== 'Provisioned');
return blockDevices.filter(d => d?.isProvisioned && d?.status?.provisionPhase !== 'Provisioned');
}
get diskStatusCount() {
@@ -1,6 +1,6 @@
import { _CLONE } from '@shell/config/query-params';
import pick from 'lodash/pick';
import { PV, LONGHORN } from '@shell/config/types';
import { PV, LONGHORN, STORAGE_CLASS } from '@shell/config/types';
import { DESCRIPTION } from '@shell/config/labels-annotations';
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
import { findBy } from '@shell/utils/array';
@@ -10,7 +10,10 @@ import { HCI, VOLUME_SNAPSHOT } from '../../types';
import HarvesterResource from '../harvester';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
const DEGRADED_ERROR = 'replica scheduling failed';
import { LONGHORN_DRIVER } from '@shell/config/types';
import { DATA_ENGINE_V2 } from '../../edit/harvesterhci.io.storage/index.vue';
const DEGRADED_ERRORS = ['replica scheduling failed', 'precheck new replica failed'];
export default class HciPv extends HarvesterResource {
applyDefaults(_, realMode) {
@@ -27,32 +30,44 @@ export default class HciPv extends HarvesterResource {
}
get availableActions() {
const out = super._availableActions;
const clone = out.find(action => action.action === 'goToClone');
let out = super._availableActions;
if (clone) {
clone.action = 'goToCloneVolume';
// Longhorn V2 provisioner do not support volume clone feature yet
if (this.storageClass.longhornVersion === DATA_ENGINE_V2) {
out = out.filter(action => action.action !== 'goToClone');
} else {
const clone = out.find(action => action.action === 'goToClone');
if (clone) {
clone.action = 'goToCloneVolume';
}
}
if (this.storageClass.provisioner !== LONGHORN_DRIVER || this.storageClass.longhornVersion !== DATA_ENGINE_V2) {
out = [
{
action: 'exportImage',
enabled: this.hasAction('export') && !this.isEncrypted,
icon: 'icon icon-copy',
label: this.t('harvester.action.exportImage')
},
{
action: 'snapshot',
enabled: this.hasAction('snapshot'),
icon: 'icon icon-backup',
label: this.t('harvester.action.snapshot'),
},
...out
];
}
return [
{
action: 'exportImage',
enabled: this.hasAction('export') && !this.isEncrypted,
icon: 'icon icon-copy',
label: this.t('harvester.action.exportImage')
},
{
action: 'cancelExpand',
enabled: this.hasAction('cancelExpand'),
icon: 'icon icon-backup',
label: this.t('harvester.action.cancelExpand')
},
{
action: 'snapshot',
enabled: this.hasAction('snapshot'),
icon: 'icon icon-backup',
label: this.t('harvester.action.snapshot'),
},
...out
];
}
@@ -91,13 +106,19 @@ export default class HciPv extends HarvesterResource {
this.metadata.annotations = pick(this.metadata.annotations, keys);
}
get storageClass() {
const inStore = this.$rootGetters['currentProduct'].inStore;
return this.$rootGetters[`${ inStore }/all`](STORAGE_CLASS).find(sc => sc.name === this.spec.storageClassName);
}
get canUpdate() {
return this.hasLink('update');
}
get stateDisplay() {
const volumeError = this.relatedPV?.metadata?.annotations?.[HCI_ANNOTATIONS.VOLUME_ERROR];
const degradedVolume = volumeError === DEGRADED_ERROR;
const degradedVolume = DEGRADED_ERRORS.includes(volumeError);
const status = this?.status?.phase === 'Bound' && !volumeError && this.isLonghornVolumeReady ? 'Ready' : 'Not Ready';
const conditions = this?.status?.conditions || [];
@@ -116,7 +137,7 @@ export default class HciPv extends HarvesterResource {
// state is similar with stateDisplay, the reason we keep this property is the status of In-use should not be displayed on vm detail page
get state() {
const volumeError = this.relatedPV?.metadata?.annotations?.[HCI_ANNOTATIONS.VOLUME_ERROR];
const degradedVolume = volumeError === DEGRADED_ERROR;
const degradedVolume = DEGRADED_ERRORS.includes(volumeError);
let status = this?.status?.phase === 'Bound' && !volumeError ? 'Ready' : 'Not Ready';
const conditions = this?.status?.conditions || [];
@@ -2,6 +2,10 @@ import { clone } from '@shell/utils/object';
import StorageClass from '@shell/models/storage.k8s.io.storageclass';
import { HCI } from '../../types';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
import { LONGHORN_DRIVER } from '@shell/config/types';
import { DATA_ENGINE_V1, DATA_ENGINE_V2 } from '../../edit/harvesterhci.io.storage/index.vue';
export const LVM_DRIVER = 'lvm.driver.harvesterhci.io';
export default class HciStorageClass extends StorageClass {
get detailLocation() {
@@ -31,4 +35,30 @@ export default class HciStorageClass extends StorageClass {
get parentNameOverride() {
return this.$rootGetters['i18n/t'](`typeLabel."${ HCI.STORAGE }"`, { count: 1 })?.trim();
}
get longhornVersion() {
if (this.provisioner === LONGHORN_DRIVER) {
return (this.parameters || {}).dataEngine || DATA_ENGINE_V1;
}
return null;
}
get provisionerDisplay() {
let key = '';
if (this.provisioner === LONGHORN_DRIVER) {
key = `harvester.storage.storageClass.longhorn.${ this.longhornVersion }.label`;
}
if (this.provisioner === LVM_DRIVER) {
key = `harvester.storage.storageClass.lvm.label`;
}
return this.$rootGetters['i18n/t'](key);
}
get isLonghornV2() {
return this.provisioner === LONGHORN_DRIVER && this.longhornVersion === DATA_ENGINE_V2;
}
}
@@ -2,6 +2,7 @@ import jsyaml from 'js-yaml';
import startCase from 'lodash/startCase';
import { HCI as HCI_ANNOTATIONS } from '../config/labels-annotations';
import HarvesterResource from './harvester';
import { HCI } from '../types';
export default class HciAddonConfig extends HarvesterResource {
get availableActions() {
@@ -106,6 +107,10 @@ export default class HciAddonConfig extends HarvesterResource {
return failedCondition?.message || super.stateDescription;
}
get parentNameOverride() {
return this.$rootGetters['i18n/t'](`typeLabel."${ HCI.ADD_ONS }"`, { count: 1 })?.trim();
}
get displayName() {
const isExperimental = this.metadata?.labels?.[HCI_ANNOTATIONS.ADDON_EXPERIMENTAL] === 'true';
@@ -15,7 +15,7 @@ export default class HciBlockDevice extends HarvesterResource {
}
get isChildPartProvisioned() {
const parts = this.childParts.filter(p => p.spec?.fileSystem?.provisioned) || [];
const parts = this.childParts.filter(p => p.isProvisioned) || [];
return parts.length > 0;
}
@@ -59,4 +59,9 @@ export default class HciBlockDevice extends HarvesterResource {
return formatting.status === 'True';
}
get isProvisioned() {
// spec.fileSystem.provisioned is deprecated
return this.spec?.fileSystem?.provisioned || this.spec?.provision;
}
}
@@ -4,6 +4,7 @@ import { HARVESTER_NAME, HARVESTER_NAME as VIRTUAL } from '@shell/config/feature
import { SETTING } from '@shell/config/settings';
export default class HciCluster extends ProvCluster {
get stateObj() {
return this._stateObj;
}
@@ -29,10 +30,6 @@ export default class HciCluster extends ProvCluster {
return false;
}
cachedHarvesterClusterVersion = '';
_uiInfo = undefined;
/**
* Fetch and cache the response for /ui-info
*
@@ -13,6 +13,7 @@ import { parseVolumeClaimTemplates } from '@pkg/utils/vm';
import { BACKUP_TYPE } from '../config/types';
import { HCI } from '../types';
import HarvesterResource from './harvester';
import { LVM_DRIVER } from './harvester/storage.k8s.io.storageclass';
export const OFF = 'Off';
@@ -87,12 +88,17 @@ const IgnoreMessages = ['pod has unbound immediate PersistentVolumeClaims'];
export default class VirtVm extends HarvesterResource {
get availableActions() {
const out = super._availableActions;
let out = super._availableActions;
const clone = out.find(action => action.action === 'goToClone');
// VM attached with Longhorn V2 volume doesn't support clone feature
if (this.longhornV2Volumes.length > 0) {
out = out.filter(action => action.action !== 'goToClone');
} else {
const clone = out.find(action => action.action === 'goToClone');
if (clone) {
clone.action = 'goToCloneVM';
if (clone) {
clone.action = 'goToCloneVM';
}
}
return [
@@ -150,7 +156,7 @@ export default class VirtVm extends HarvesterResource {
},
{
action: 'takeVMSnapshot',
enabled: !!this.actions?.backup,
enabled: !!this.actions?.backup && !this.longhornV2Volumes.length,
icon: 'icon icon-snapshot',
label: this.t('harvester.action.vmSnapshot')
},
@@ -593,16 +599,26 @@ export default class VirtVm extends HarvesterResource {
return vmis.find(VMI => VMI.id === this.id);
}
get encryptedVolumeType() {
const inStore = this.productInStore;
const pvcs = this.$rootGetters[`${ inStore }/all`](PVC);
get volumes() {
const pvcs = this.$rootGetters[`${ this.productInStore }/all`](PVC);
const volumeClaimNames = this.spec.template.spec.volumes?.map(v => v.persistentVolumeClaim?.claimName).filter(v => !!v) || [];
const volumes = pvcs.filter(pvc => volumeClaimNames.includes(pvc.metadata.name));
if (volumes.every(vol => vol.isEncrypted)) {
return pvcs.filter(pvc => volumeClaimNames.includes(pvc.metadata.name));
}
get lvmVolumes() {
return this.volumes.filter(volume => volume.storageClass.provisioner === LVM_DRIVER);
}
get longhornV2Volumes() {
return this.volumes.filter(volume => volume.storageClass.isLonghornV2);
}
get encryptedVolumeType() {
if (this.volumes.every(vol => vol.isEncrypted)) {
return 'all';
} else if (volumes.some(vol => vol.isEncrypted)) {
} else if (this.volumes.some(vol => vol.isEncrypted)) {
return 'partial';
} else {
return 'none';