Skip to content

Commit

Permalink
CNV-5024: Inform for pending changes in VM details view
Browse files Browse the repository at this point in the history
Add alerts when the user make changes in the VM details view
while the VM is running informing to restart the VM for
the changes to apply.

Additionally, the changes aren't reflected immediately on the screen
while the VM is running, like they used to be, but change after the
VM restarts. This way the screen represents the current configuration
rather than the 'future' configuration.

Signed-off-by: Ido Rosenzwig <irosenzw@redhat.com>

CNV-5024# Please enter the commit message for your changes. Lines starting
  • Loading branch information
irosenzw committed May 31, 2020
1 parent 246f7fd commit ed6b730
Show file tree
Hide file tree
Showing 15 changed files with 271 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import { k8sPatch } from '@console/internal/module/k8s';
import { PatchBuilder } from '@console/shared/src/k8s';
import { BootableDeviceType } from '../../../types';
import { VMLikeEntityKind } from '../../../types/vmLike';
import { getVMLikeModel, getDevices } from '../../../selectors/vm';
import { getVMLikeModel, getDevices, asVM, isVMRunningOrExpectedRunning } from '../../../selectors/vm';
import { getVMLikePatches } from '../../../k8s/patches/vm-template';
import { BootOrder, deviceKey } from '../../boot-order';
import { DeviceType } from '../../../constants';
import { ModalFooter } from '../modal/modal-footer';
import { pendingChangesAlert } from '../../vms/utils';

import './boot-order-modal.scss';

Expand Down Expand Up @@ -146,6 +147,7 @@ const BootOrderModalComponent = ({
<Modal
title={title}
isOpen={isOpen}
description={isVMRunningOrExpectedRunning(asVM(vmLikeEntity)) && pendingChangesAlert()}
isSmall
onClose={() => setOpen(false)}
footer={footer}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import * as _ from 'lodash';
import { Form, Button, Tooltip, Alert, Text, TextVariants } from '@patternfly/react-core';
import { Form, Button, Tooltip, Text, TextVariants } from '@patternfly/react-core';
import {
FirehoseResult,
HandlePromiseProps,
Expand Down Expand Up @@ -30,6 +30,7 @@ import './cdrom-modal.scss';
import { CD, CDMap } from './types';
import { VMKind } from '../../../types/vm';
import { useStorageClassConfigMap } from '../../../hooks/storage-class-config-map';
import { pendingChangesAlert } from '../../vms/utils';

export const AddCDButton = ({ className, text, onClick, isDisabled }: AddCDButtonProps) => (
<div className={className}>
Expand Down Expand Up @@ -117,11 +118,9 @@ export const CDRomModal = withHandlePromise((props: CDRomModalProps) => {
);

const [cds, setCDs] = React.useState<CDMap>(mapCDsToSource(getCDRoms(vm)));
const [showRestartAlert, setShowRestartAlert] = React.useState<boolean>(false);
const [shouldPatch, setShouldPatch] = React.useState<boolean>(false);

const onCDChange = (cdName: string, key: string, value: string) => {
setShowRestartAlert(true);
setShouldPatch(true);
const cd = { ...cds[cdName], [key]: value };
if (key === StorageType.URL) {
Expand All @@ -142,7 +141,6 @@ export const CDRomModal = withHandlePromise((props: CDRomModalProps) => {
name,
newCD: true,
};
setShowRestartAlert(true);
setShouldPatch(true);
setCDs({ ...cds, [name]: newCD });
};
Expand Down Expand Up @@ -179,13 +177,7 @@ export const CDRomModal = withHandlePromise((props: CDRomModalProps) => {
<div className="modal-content">
<ModalTitle>Edit CD-ROMs</ModalTitle>
<ModalBody>
{showRestartAlert && isVMRunningOrExpectedRunning(vm) && (
<Alert
variant="info"
isInline
title="Changes will be applied when the virtual machine has been restarted"
/>
)}
{isVMRunningOrExpectedRunning(vm) && pendingChangesAlert()}
<Form className="pf-l-grid pf-m-gutter">
{_.size(cds) > 0 ? (
cdsValue.map((cd, i) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
getMemory,
getVMLikeModel,
vCPUCount,
isVMRunningOrExpectedRunning,
} from '../../../selectors/vm';
import { getUpdateFlavorPatches } from '../../../k8s/patches/vm/vm-patches';
import { CUSTOM_FLAVOR } from '../../../constants';
Expand All @@ -40,6 +41,7 @@ import { flavorSort } from '../../../utils/sort';
import { getTemplateFlavors } from '../../../selectors/vm-template/advanced';
import { getVMTemplateNamespacedName } from '../../../selectors/vm-template/selectors';
import { toUIFlavor, isCustomFlavor } from '../../../selectors/vm-like/flavor';
import { pendingChangesAlert } from '../../vms/utils';

const getId = (field: string) => `vm-flavor-modal-${field}`;

Expand Down Expand Up @@ -109,6 +111,7 @@ const VMFlavorModal = withHandlePromise((props: VMFlavornModalProps) => {
<div className="modal-content">
<ModalTitle>Edit Flavor</ModalTitle>
<ModalBody>
{isVMRunningOrExpectedRunning(vm) && pendingChangesAlert()}
<Form>
<FormRow title="Flavor" fieldId={getId('flavor')} isRequired>
<FormSelect
Expand Down
6 changes: 6 additions & 0 deletions frontend/packages/kubevirt-plugin/src/components/vms/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ export type VMTabProps = {
export type VMLikeEntityTabProps = {
obj?: VMGenericLikeEntityKind;
};

export enum IsPendingChange {
flavor = 'Flavor',
cdroms = 'CD-ROMs',
bootOrder = 'Boot Order',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.kubevirt-vm-details__restart_required-class-alert {
margin-bottom: var(--pf-global--spacer--sm);
margin-top: var(--pf-global--spacer--sm);
}
101 changes: 101 additions & 0 deletions frontend/packages/kubevirt-plugin/src/components/vms/utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import * as React from 'react';
import { VMWrapper } from '../../k8s/wrapper/vm/vm-wrapper';
import { VMIWrapper } from '../../k8s/wrapper/vm/vmi-wrapper';
import * as _ from 'lodash';
import { getPVCSourceByDisk } from '../../selectors/vm/selectors';
import { getVMIPVCSourceByDisk } from '../../selectors/vmi/selectors';
import { BootableDeviceType } from '../../types/types';
import { IsPendingChange } from './types';
import { VMKind, VMIKind } from '../../types/vm';
import { Alert, AlertVariant } from '@patternfly/react-core';

import './utils.scss';
import { MODAL_RESTART_IS_REQUIRED } from '../../strings/vm/status';

export const isFlavorChanged = (vm: VMWrapper, vmi: VMIWrapper): boolean => {
if (!vm || !vmi) {
return false;
}

return vm.getFlavor() !== vmi.getFlavor();
};

export const isCDROMChanged = (vm: VMWrapper, vmi: VMIWrapper): boolean => {
if (!vm || !vmi) {
return false;
}

const vmPVCs: string[] = vm
.getCDROMs()
.map((cd) => getPVCSourceByDisk(vm.getVMObj(), cd.name))
.sort();

const vmiPVCs: string[] = vmi
.getCDROMs()
.map((cd) => getVMIPVCSourceByDisk(vmi.getVMIObj(), cd.name))
.sort();

if (vmiPVCs.length === 0) {
return false;
}

if (vmPVCs.length !== vmiPVCs.length) {
return true;
}

return !vmPVCs.every((value, index) => value === vmiPVCs[index]);
};

export const isBootOrderChanged = (vm: VMWrapper, vmi: VMIWrapper): boolean => {
if (!vm || !vmi) {
return false;
}

const vmBootOrder: BootableDeviceType[] = _.sortBy(
vm.getLabeledDevices().filter((dev) => dev.value.bootOrder),
'value.bootOrder',
);

const vmiBootOrder: BootableDeviceType[] = _.sortBy(
vmi.getLabeledDevices().filter((dev) => dev.value.bootOrder),
'value.bootOrder',
);

if (vmiBootOrder.length === 0) {
return false;
}

if (vmBootOrder.length !== vmiBootOrder.length) {
return true;
}

return !vmBootOrder.every(
(device, index) =>
device.type === vmiBootOrder[index].type &&
device.typeLabel === vmiBootOrder[index].typeLabel &&
device.value.bootOrder === vmiBootOrder[index].value.bootOrder &&
device.value.name === vmiBootOrder[index].value.name,
);
};

export const detectNextRunChanges = (vm: VMKind, vmi: VMIKind) => {
const vmWrapper = new VMWrapper(vm);
const vmiWrapper = new VMIWrapper(vmi);

return {
[IsPendingChange.flavor]: !!vmi && isFlavorChanged(vmWrapper, vmiWrapper),
[IsPendingChange.cdroms]: !!vmi && isCDROMChanged(vmWrapper, vmiWrapper),
[IsPendingChange.bootOrder]: !!vmi && isBootOrderChanged(vmWrapper, vmiWrapper),
};
};

export const pendingChangesAlert = () => (
<Alert
title="Pending Changes"
isInline
variant={AlertVariant.info}
className="kubevirt-vm-details__restart_required-class-alert"
>
{MODAL_RESTART_IS_REQUIRED}
</Alert>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.kubevirt-vm-details__pending-changes-class-alert {
margin-bottom: var(--pf-global--spacer--sm);
margin-top: var(--pf-global--spacer--sm);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ import { getLoadedData, getResource } from '../../utils';
import { VirtualMachineInstanceModel, VirtualMachineModel } from '../../models';
import { getServicesForVmi } from '../../selectors/service';
import { VMResourceSummary, VMDetailsList, VMSchedulingList } from './vm-resource';
import { VMTabProps } from './types';
import { VMTabProps, IsPendingChange } from './types';
import { getVMStatus } from '../../statuses/vm/vm-status';
import { VMStatusBundle } from '../../statuses/vm/types';
import { isVM, isVMI } from '../../selectors/check-type';
import { detectNextRunChanges } from './utils';
import { Alert, AlertVariant, List, ListItem, Button } from '@patternfly/react-core';

import './vm-details.scss';

export const VMDetailsFirehose: React.FC<VMTabProps> = ({
obj: objProp,
Expand Down Expand Up @@ -73,16 +77,58 @@ export const VMDetailsFirehose: React.FC<VMTabProps> = ({

export const VMDetails: React.FC<VMDetailsProps> = (props) => {
const { kindObj, vm, vmi, pods, vmStatusBundle, templates, ...restProps } = props;
const [openBootOrderModal, setOpenBootOrderModal] = React.useState(false);
const [openCDROMModal, setOpenCDROMModal] = React.useState(false);
const [openFlavorModal, setOpenFlavorModal] = React.useState(false);

const vmiLike = kindObj === VirtualMachineModel ? vm : vmi;
const vmServicesData = getServicesForVmi(getLoadedData(props.services, []), vmi);
const canUpdate = useAccessReview(asAccessReview(kindObj, vmiLike || {}, 'patch')) && !!vmiLike;
const vmConfChanges = detectNextRunChanges(vm, vmi);
const isVMRequireRestart = !!vmi && Object.values(vmConfChanges).some((x) => !!x);

const openModal = (key) => {
switch (key) {
case IsPendingChange.flavor:
setOpenFlavorModal(true);
return;
case IsPendingChange.cdroms:
setOpenCDROMModal(true);
return;
case IsPendingChange.bootOrder:
setOpenBootOrderModal(true);
break;
default:
break;
}
};

return (
<StatusBox data={vmiLike} {...restProps}>
<ScrollToTopOnMount />
<div className="co-m-pane__body">
<SectionHeading text={`${kindObj.label} Details`} />
{isVMRequireRestart && (
<Alert
title="Pending Changes"
isInline
variant={AlertVariant.warning}
className="kubevirt-vm-details__pending-changes-class-alert"
>
<List>
{Object.keys(vmConfChanges).map(
(key) =>
vmConfChanges[key] && (
<ListItem key={key}>
<Button onClick={() => openModal(key)} isInline variant="link">
{key}
</Button>
</ListItem>
),
)}
</List>
</Alert>
)}
<div className="row">
<div className="col-sm-6">
<VMResourceSummary
Expand All @@ -101,14 +147,25 @@ export const VMDetails: React.FC<VMDetailsProps> = (props) => {
vmi={vmi}
pods={pods}
vmStatusBundle={vmStatusBundle}
openBootOrderModal={openBootOrderModal}
openCDROMModal={openCDROMModal}
setOpenBootOrderModal={setOpenBootOrderModal}
setOpenCDROMModal={setOpenCDROMModal}
/>
</div>
</div>
</div>
<div className="co-m-pane__body">
<SectionHeading text="Scheduling and resources requirements" />
<div className="row">
<VMSchedulingList kindObj={kindObj} canUpdateVM={canUpdate} vm={vm} vmi={vmi} />
<VMSchedulingList
kindObj={kindObj}
canUpdateVM={canUpdate}
vm={vm}
vmi={vmi}
openFlavorModal={openFlavorModal}
setOpenFlavorModal={setOpenFlavorModal}
/>
</div>
</div>
<div className="co-m-pane__body">
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
@import "./resource-summary-description";

.kubevirt-vm-details__vm-details-item {
padding-left: var(--pf-global--spacer--sm);
padding-right: var(--pf-global--spacer--sm);
}

0 comments on commit ed6b730

Please sign in to comment.