mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-02-04 06:51:44 +00:00
feat: add resume button for upgrade paused node (#698)
* feat: add nodeUpgradeOption setting Signed-off-by: Andy Lee <andy.lee@suse.com> * feat: add resume button when node paused Signed-off-by: Andy Lee <andy.lee@suse.com> * feat: add feature flag in v1.7.0 Signed-off-by: Andy Lee <andy.lee@suse.com> --------- Signed-off-by: Andy Lee <andy.lee@suse.com>
This commit is contained in:
parent
473c1ba355
commit
77599900b5
@ -1,6 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import Collapse from '@shell/components/Collapse';
|
import Collapse from '@shell/components/Collapse';
|
||||||
import PercentageBar from '@shell/components/PercentageBar';
|
import PercentageBar from '@shell/components/PercentageBar';
|
||||||
|
import { HCI } from '../types';
|
||||||
|
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterUpgradeProgressList',
|
name: 'HarvesterUpgradeProgressList',
|
||||||
@ -25,13 +27,45 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async fetch() {
|
||||||
|
await this.$store.dispatch('harvester/findAll', { type: HCI.UPGRADE });
|
||||||
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return { open: true };
|
return { open: true };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
showResumeButton() {
|
||||||
|
return this.title === 'Upgrading Node';
|
||||||
|
},
|
||||||
|
latestUpgradeCR() {
|
||||||
|
return this.$store.getters['harvester/all'](HCI.UPGRADE).find( (U) => U.isLatestUpgrade);
|
||||||
|
},
|
||||||
|
resumeUpgradePausedNodeEnabled() {
|
||||||
|
return this.$store.getters['harvester-common/getFeatureEnabled']('resumeUpgradePausedNode');
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleSwitch() {
|
handleSwitch() {
|
||||||
this.open = !this.open;
|
this.open = !this.open;
|
||||||
|
},
|
||||||
|
async resumeNodeUpgrade(nodeName) {
|
||||||
|
if (!this.latestUpgradeCR || !nodeName) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const upgradePauseMapString = this.latestUpgradeCR.metadata.annotations[HCI_ANNOTATIONS.NODE_UPGRADE_PAUSE_MAP] || '{}';
|
||||||
|
const upgradePauseMap = JSON.parse(upgradePauseMapString);
|
||||||
|
|
||||||
|
// update the upgrade CR annotation harvesterhci.io/node-upgrade-pause-map to unpause the node upgrade process
|
||||||
|
upgradePauseMap[`${ nodeName }`] = 'unpause';
|
||||||
|
this.latestUpgradeCR.setAnnotation(HCI_ANNOTATIONS.NODE_UPGRADE_PAUSE_MAP, JSON.stringify(upgradePauseMap));
|
||||||
|
await this.latestUpgradeCR.save();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`unable to update harvester upgrade CR annotations: ${ this.latestUpgradeCR.id }.`, e); // eslint-disable-line no-console
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -63,12 +97,28 @@ export default {
|
|||||||
v-for="(item, i) in list"
|
v-for="(item, i) in list"
|
||||||
:key="i"
|
:key="i"
|
||||||
>
|
>
|
||||||
|
<div class="upgrade-node-header">
|
||||||
|
<div class="upgrade-node-title">
|
||||||
<p>
|
<p>
|
||||||
{{ item.name }} <span
|
{{ item.name }}
|
||||||
|
</p>
|
||||||
|
<span
|
||||||
class="status"
|
class="status"
|
||||||
:class="{ [item.state]: true }"
|
:class="{ [item.state]: true }"
|
||||||
>{{ item.state }}</span>
|
>
|
||||||
</p>
|
{{ item.state }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
v-if="showResumeButton && resumeUpgradePausedNodeEnabled && item.state === 'Node-upgrade paused'"
|
||||||
|
type="button"
|
||||||
|
class="btn bg-info btn-sm"
|
||||||
|
data-testid="add-item"
|
||||||
|
@click="resumeNodeUpgrade(item.name)"
|
||||||
|
>
|
||||||
|
{{ t('action.resume') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<PercentageBar
|
<PercentageBar
|
||||||
:model-value="item.percent"
|
:model-value="item.percent"
|
||||||
preferred-direction="MORE"
|
preferred-direction="MORE"
|
||||||
@ -102,10 +152,21 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.custom-content {
|
.custom-content {
|
||||||
margin-bottom: 14px;
|
.upgrade-node-title {
|
||||||
p {
|
flex: 1 0 80%;
|
||||||
|
margin-right: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.upgrade-node-header {
|
||||||
|
display:flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
margin-bottom: 14px;
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
@ -117,6 +178,8 @@ export default {
|
|||||||
}
|
}
|
||||||
.warning {
|
.warning {
|
||||||
color: var(--error);
|
color: var(--error);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import { LabeledInput } from '@components/Form/LabeledInput';
|
|||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
import { RadioGroup } from '@components/Form/Radio';
|
import { RadioGroup } from '@components/Form/Radio';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
import { allHash } from '@shell/utils/promise';
|
||||||
|
import { NODE } from '@shell/config/types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterUpgradeConfig',
|
name: 'HarvesterUpgradeConfig',
|
||||||
@ -15,6 +17,13 @@ export default {
|
|||||||
},
|
},
|
||||||
mixins: [CreateEditView],
|
mixins: [CreateEditView],
|
||||||
|
|
||||||
|
async fetch() {
|
||||||
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
|
const hash = { nodes: this.$store.dispatch(`${ inStore }/findAll`, { type: NODE }) };
|
||||||
|
|
||||||
|
await allHash(hash);
|
||||||
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
let parseDefaultValue = {};
|
let parseDefaultValue = {};
|
||||||
|
|
||||||
@ -39,7 +48,25 @@ export default {
|
|||||||
{ value: 'skip', label: 'skip' },
|
{ value: 'skip', label: 'skip' },
|
||||||
{ value: 'parallel', label: 'parallel' }
|
{ value: 'parallel', label: 'parallel' }
|
||||||
];
|
];
|
||||||
}
|
},
|
||||||
|
nodeUpgradeOptions() {
|
||||||
|
return [
|
||||||
|
{ value: 'auto', label: 'auto' },
|
||||||
|
{ value: 'manual', label: 'manual' }
|
||||||
|
];
|
||||||
|
},
|
||||||
|
nodesOptions() {
|
||||||
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
|
const nodes = this.$store.getters[`${ inStore }/all`](NODE);
|
||||||
|
|
||||||
|
return nodes.map((node) => ({ value: node.id, label: node.name }));
|
||||||
|
},
|
||||||
|
showPauseNodes() {
|
||||||
|
return this.parseDefaultValue.nodeUpgradeOption?.strategy?.mode === 'manual';
|
||||||
|
},
|
||||||
|
resumeUpgradePausedNodeEnabled() {
|
||||||
|
return this.$store.getters['harvester-common/getFeatureEnabled']('resumeUpgradePausedNode');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
@ -48,6 +75,18 @@ export default {
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
normalizeValue(obj) {
|
normalizeValue(obj) {
|
||||||
|
// handle nodeUpgradeOption.strategy
|
||||||
|
if (obj?.nodeUpgradeOption?.strategy?.mode === 'auto') {
|
||||||
|
delete obj.nodeUpgradeOption.strategy.pauseNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj?.nodeUpgradeOption?.strategy?.mode === 'manual') {
|
||||||
|
if (!Array.isArray(obj.nodeUpgradeOption.strategy.pauseNodes)) {
|
||||||
|
obj.nodeUpgradeOption.strategy.pauseNodes = this.nodesOptions.map((node) => node.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle imagePreloadOption.strategy
|
||||||
if (!obj.imagePreloadOption) {
|
if (!obj.imagePreloadOption) {
|
||||||
obj.imagePreloadOption = { strategy: { type: 'sequential' } };
|
obj.imagePreloadOption = { strategy: { type: 'sequential' } };
|
||||||
}
|
}
|
||||||
@ -105,8 +144,8 @@ export default {
|
|||||||
this.update();
|
this.update();
|
||||||
},
|
},
|
||||||
deep: true
|
deep: true
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -144,6 +183,28 @@ export default {
|
|||||||
:labels="[t('generic.enabled'), t('generic.disabled')]"
|
:labels="[t('generic.enabled'), t('generic.disabled')]"
|
||||||
@update:value="update"
|
@update:value="update"
|
||||||
/>
|
/>
|
||||||
|
<div v-if="resumeUpgradePausedNodeEnabled">
|
||||||
|
<label class="mb-5"><b>{{ t('harvester.setting.upgrade.nodeUpgradeOption') }}</b></label>
|
||||||
|
<LabeledSelect
|
||||||
|
v-model:value="parseDefaultValue.nodeUpgradeOption.strategy.mode"
|
||||||
|
class="mb-20 label-select"
|
||||||
|
:mode="mode"
|
||||||
|
:label="t('harvester.setting.upgrade.strategy')"
|
||||||
|
:options="nodeUpgradeOptions"
|
||||||
|
@update:value="update"
|
||||||
|
/>
|
||||||
|
<LabeledSelect
|
||||||
|
v-if="showPauseNodes"
|
||||||
|
v-model:value="parseDefaultValue.nodeUpgradeOption.strategy.pauseNodes"
|
||||||
|
class="mb-20 label-select"
|
||||||
|
:clearable="true"
|
||||||
|
:multiple="true"
|
||||||
|
:mode="mode"
|
||||||
|
:label="t('harvester.setting.upgrade.pauseNodes')"
|
||||||
|
:options="nodesOptions"
|
||||||
|
@update:value="update"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="errors.length"
|
v-if="errors.length"
|
||||||
class="error"
|
class="error"
|
||||||
|
|||||||
@ -54,8 +54,11 @@ const FEATURE_FLAGS = {
|
|||||||
'lhV2VolExpansion',
|
'lhV2VolExpansion',
|
||||||
'l2VlanTrunkMode',
|
'l2VlanTrunkMode',
|
||||||
'kubevirtMigration',
|
'kubevirtMigration',
|
||||||
'hotplugNic'
|
'hotplugNic',
|
||||||
]
|
'resumeUpgradePausedNode',
|
||||||
|
],
|
||||||
|
'v1.7.1': [],
|
||||||
|
'v1.8.0': []
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateFeatureFlags = () => {
|
const generateFeatureFlags = () => {
|
||||||
|
|||||||
@ -78,4 +78,5 @@ export const HCI = {
|
|||||||
VOLUME_MODE_ACCESS_MODES: 'cdi.harvesterhci.io/storageProfileVolumeModeAccessModes',
|
VOLUME_MODE_ACCESS_MODES: 'cdi.harvesterhci.io/storageProfileVolumeModeAccessModes',
|
||||||
VOLUME_SNAPSHOT_CLASS: 'cdi.harvesterhci.io/storageProfileVolumeSnapshotClass',
|
VOLUME_SNAPSHOT_CLASS: 'cdi.harvesterhci.io/storageProfileVolumeSnapshotClass',
|
||||||
MAC_ADDRESS: 'harvesterhci.io/mac-address',
|
MAC_ADDRESS: 'harvesterhci.io/mac-address',
|
||||||
|
NODE_UPGRADE_PAUSE_MAP: 'harvesterhci.io/node-upgrade-pause-map',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1295,7 +1295,10 @@ harvester:
|
|||||||
deleteImage: Please select an image to delete.
|
deleteImage: Please select an image to delete.
|
||||||
deleteSuccess: "{name} deleted successfully."
|
deleteSuccess: "{name} deleted successfully."
|
||||||
imagePreloadStrategy: Image Preload Strategy
|
imagePreloadStrategy: Image Preload Strategy
|
||||||
|
nodeUpgradeOption: Node Upgrade Option
|
||||||
restoreVM: Restore VM
|
restoreVM: Restore VM
|
||||||
|
strategy: Strategy
|
||||||
|
pauseNodes: Pause Nodes
|
||||||
strategyType: Strategy Type
|
strategyType: Strategy Type
|
||||||
concurrency: Concurrency
|
concurrency: Concurrency
|
||||||
harvesterMonitoring:
|
harvesterMonitoring:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user