-
Notifications
You must be signed in to change notification settings - Fork 670
Add CPU Pinning modal and functionality #3805
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| export const DEDICATED_RESOURCES = 'Dedicated Resources'; | ||
| export const RESOURCE_PINNED = 'Workload scheduled with dedicated resources (guaranteed policy)'; | ||
| export const RESOURCE_NOT_PINNED = 'No Dedicated resources applied'; | ||
| export const RESOURCE_NO_NODES_AVAILABLE = 'There are no qualified Nodes with the required labels'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| .kubevirt-cpu-pinning__checkbox { | ||
| margin-top: var(--pf-global--spacer--md); | ||
| margin-bottom: var(--pf-global--spacer--md); | ||
| } | ||
|
|
||
| .kubevirt-cpu-pinning__helper-text { | ||
| color: var(--pf-global--Color--200); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| import * as React from 'react'; | ||
| import { Checkbox, Modal, Text, TextVariants } from '@patternfly/react-core'; | ||
| import { k8sPatch } from '@console/internal/module/k8s'; | ||
| import { NodeModel } from '@console/internal/models'; | ||
| import { getLabel } from '@console/shared'; | ||
| import { | ||
| Firehose, | ||
| withHandlePromise, | ||
| HandlePromiseProps, | ||
| FirehoseResult, | ||
| Label, | ||
| } from '@console/internal/components/utils'; | ||
| import { ModalFooter } from '../modal/modal-footer'; | ||
| import { getVMLikeModel, isDedicatedCPUPlacement, asVM } from '../../../selectors/vm'; | ||
| import { getDedicatedCpuPatch } from '../../../k8s/patches/vm/vm-cpu-patches'; | ||
| import { VMLikeEntityKind } from '../../../types'; | ||
| import { getLoadedData, isLoaded, getLoadError } from '../../../utils'; | ||
| import { RESOURCE_NO_NODES_AVAILABLE, DEDICATED_RESOURCES } from './consts'; | ||
| import './dedicated-resources-modal.scss'; | ||
|
|
||
| const ResourceModal = withHandlePromise<ResourceModalProps>( | ||
| ({ vmLikeEntity, nodes, isOpen, setOpen, handlePromise, inProgress, errorMessage }) => { | ||
| const isLoading = !isLoaded(nodes); | ||
| const loadError = getLoadError(nodes, NodeModel); | ||
| const isCPUPinned = isDedicatedCPUPlacement(asVM(vmLikeEntity)); | ||
| const loadedNodes = getLoadedData(nodes, []); | ||
|
|
||
| const [isPinned, setIsPinned] = React.useState<boolean>(isCPUPinned); | ||
| const [showPatchError, setPatchError] = React.useState<boolean>(false); | ||
| const isNodeAvailable = loadedNodes.some((node) => getLabel(node, 'cpumanager') === 'true'); | ||
|
|
||
| const submit = async () => { | ||
| if (isPinned !== isCPUPinned) { | ||
| handlePromise( | ||
| k8sPatch( | ||
| getVMLikeModel(vmLikeEntity), | ||
| vmLikeEntity, | ||
| await getDedicatedCpuPatch(vmLikeEntity, isPinned), | ||
| ), | ||
| ) | ||
| .then(() => setOpen(false)) | ||
| .catch(() => setPatchError(true)); | ||
| } else { | ||
| setOpen(false); | ||
| } | ||
| }; | ||
| const footer = ( | ||
| <ModalFooter | ||
| className="" | ||
| warningMessage={!loadError && !isNodeAvailable && RESOURCE_NO_NODES_AVAILABLE} | ||
| errorMessage={showPatchError && errorMessage} | ||
| inProgress={inProgress || isLoading} | ||
| isSimpleError={!isNodeAvailable} | ||
| onSubmit={submit} | ||
| onCancel={() => setOpen(false)} | ||
| submitButtonText="Save" | ||
| /> | ||
| ); | ||
|
|
||
| return ( | ||
| <Modal | ||
| width="50%" | ||
| title={DEDICATED_RESOURCES} | ||
| isOpen={isOpen} | ||
| onClose={() => setOpen(false)} | ||
| footer={footer} | ||
| isFooterLeftAligned | ||
| > | ||
| <Checkbox | ||
| className="kubevirt-cpu-pinning__checkbox" | ||
| label="Schedule this workload with dedicated resources (guaranteed policy)" | ||
| isChecked={isPinned} | ||
| isDisabled={isLoading} | ||
| onChange={setIsPinned} | ||
| id="cpu-pinning-checkbox" | ||
| /> | ||
| <Text className="kubevirt-cpu-pinning__helper-text" component={TextVariants.small}> | ||
| Available only on Nodes with labels{' '} | ||
| <Label kind={NodeModel.kind} name="cpumanager" value="true" expand /> | ||
| </Text> | ||
| </Modal> | ||
| ); | ||
| }, | ||
| ); | ||
|
|
||
| type ResourceModalProps = HandlePromiseProps & { | ||
| vmLikeEntity: VMLikeEntityKind; | ||
| isOpen: boolean; | ||
| nodes?: FirehoseResult<VMLikeEntityKind[]>; | ||
| setOpen: (isOpen: boolean) => void; | ||
| }; | ||
|
|
||
| export const DedicatedResourcesModal: React.FC<DedicatedResourcesModalProps> = (props) => { | ||
| const { vmLikeEntity, ...restProps } = props; | ||
|
|
||
| const resources = [ | ||
| { | ||
| kind: NodeModel.kind, | ||
| isList: true, | ||
| namespaced: false, | ||
| prop: 'nodes', | ||
| }, | ||
| ]; | ||
|
|
||
| return ( | ||
| <Firehose resources={resources}> | ||
| <ResourceModal vmLikeEntity={vmLikeEntity} {...restProps} /> | ||
| </Firehose> | ||
| ); | ||
| }; | ||
|
|
||
| type DedicatedResourcesModalProps = { | ||
| vmLikeEntity: VMLikeEntityKind; | ||
| isOpen: boolean; | ||
| setOpen: (isOpen: boolean) => void; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| .kubevirt-create-nic-modal__buttons { | ||
| padding: 0; | ||
| text-align: left; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| import * as React from 'react'; | ||
| import { Alert, Button, ButtonVariant } from '@patternfly/react-core'; | ||
| import classNames from 'classnames'; | ||
| import { Alert, Button, ButtonVariant, AlertProps } from '@patternfly/react-core'; | ||
| import { LoadingInline } from '@console/internal/components/utils'; | ||
| import { prefixedID } from '../../../utils'; | ||
|
|
||
|
|
@@ -20,13 +21,15 @@ export const ModalErrorMessage: React.FC<ModalErrorMessageProps> = ({ message }) | |
| </Alert> | ||
| ); | ||
|
|
||
| type ModalSimpleErrorMessageProps = { | ||
| type ModalSimpleMessageProps = { | ||
| message: string; | ||
| variant?: AlertProps['variant']; | ||
| }; | ||
|
|
||
| export const ModalSimpleErrorMessage: React.FC<ModalSimpleErrorMessageProps> = ({ message }) => ( | ||
| <Alert isInline className="co-alert" variant="danger" title={message} /> | ||
| ); | ||
| export const ModalSimpleMessage: React.FC<ModalSimpleMessageProps> = ({ | ||
| message, | ||
| variant = 'danger', | ||
| }) => <Alert isInline className="co-alert" variant={variant} title={message} />; | ||
|
|
||
| type ModalInfoMessageProps = { | ||
| title: string; | ||
|
|
@@ -41,7 +44,9 @@ export const ModalInfoMessage: React.FC<ModalInfoMessageProps> = ({ title, child | |
|
|
||
| type ModalFooterProps = { | ||
| id?: string; | ||
| className?: string; | ||
| errorMessage?: string; | ||
| warningMessage?: string; | ||
| isSimpleError?: boolean; | ||
| onSubmit: (e) => void; | ||
| onCancel: (e) => void; | ||
|
|
@@ -55,7 +60,9 @@ type ModalFooterProps = { | |
|
|
||
| export const ModalFooter: React.FC<ModalFooterProps> = ({ | ||
| id, | ||
| className = '', | ||
| errorMessage = null, | ||
|
||
| warningMessage = null, | ||
| isDisabled = false, | ||
| inProgress = false, | ||
| isSimpleError = false, | ||
|
|
@@ -66,8 +73,16 @@ export const ModalFooter: React.FC<ModalFooterProps> = ({ | |
| infoMessage = null, | ||
| infoTitle = null, | ||
| }) => ( | ||
| <footer className="co-m-btn-bar modal-footer kubevirt-create-nic-modal__buttons"> | ||
| {errorMessage && isSimpleError && <ModalSimpleErrorMessage message={errorMessage} />} | ||
| <footer | ||
| className={classNames( | ||
| 'co-m-btn-bar modal-footer kubevirt-create-nic-modal__buttons', | ||
| className, | ||
| )} | ||
| > | ||
| {warningMessage && isSimpleError && ( | ||
| <ModalSimpleMessage message={warningMessage} variant="warning" /> | ||
| )} | ||
| {errorMessage && isSimpleError && <ModalSimpleMessage message={errorMessage} />} | ||
| {errorMessage && !isSimpleError && <ModalErrorMessage message={errorMessage} />} | ||
| {infoTitle && <ModalInfoMessage title={infoTitle}>{infoMessage}</ModalInfoMessage>} | ||
| <Button | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,12 +6,14 @@ import { getBasicID, prefixedID } from '../../utils'; | |
| import { vmDescriptionModal } from '../modals/vm-description-modal'; | ||
| import { BootOrderModal } from '../modals/boot-order-modal'; | ||
| import { VMCDRomModal } from '../modals/cdrom-vm-modal'; | ||
| import { DedicatedResourcesModal } from '../modals/dedicated-resources-modal/dedicated-resources-modal'; | ||
| import { getDescription } from '../../selectors/selectors'; | ||
| import { | ||
| getCDRoms, | ||
| getOperatingSystemName, | ||
| getOperatingSystem, | ||
| getWorkloadProfile, | ||
| isDedicatedCPUPlacement, | ||
| } from '../../selectors/vm/selectors'; | ||
| import { getVMTemplateNamespacedName } from '../../selectors/vm-template/selectors'; | ||
| import { vmFlavorModal } from '../modals'; | ||
|
|
@@ -22,6 +24,11 @@ import { DiskSummary } from '../vm-disks/disk-summary'; | |
| import { asVM, getDevices } from '../../selectors/vm'; | ||
| import { BootOrderSummary } from '../boot-order'; | ||
| import { V1alpha1DataVolume } from '../../types/vm/disk/V1alpha1DataVolume'; | ||
| import { | ||
| RESOURCE_PINNED, | ||
| RESOURCE_NOT_PINNED, | ||
| DEDICATED_RESOURCES, | ||
| } from '../modals/dedicated-resources-modal/consts'; | ||
| import { VMTemplateLink } from './vm-template-link'; | ||
| import { TemplateSource } from './vm-template-source'; | ||
|
|
||
|
|
@@ -83,11 +90,15 @@ export const VMTemplateDetailsList: React.FC<VMTemplateResourceListProps> = ({ | |
| canUpdateTemplate, | ||
| }) => { | ||
| const [isBootOrderModalOpen, setBootOrderModalOpen] = React.useState<boolean>(false); | ||
| const [isDedicatedResourcesModalOpen, setDedicatedResourcesModalOpen] = React.useState<boolean>( | ||
| false, | ||
| ); | ||
|
|
||
| const id = getBasicID(template); | ||
| const devices = getDevices(template); | ||
| const cds = getCDRoms(asVM(template)); | ||
| const flavorText = getFlavorText(template); | ||
| const isCPUPinned = isDedicatedCPUPlacement(asVM(template)); | ||
|
|
||
| return ( | ||
| <dl className="co-m-pane__details"> | ||
|
|
@@ -127,6 +138,21 @@ export const VMTemplateDetailsList: React.FC<VMTemplateResourceListProps> = ({ | |
| </EditButton> | ||
| </VMDetailsItem> | ||
|
|
||
| <VMDetailsItem | ||
| title={DEDICATED_RESOURCES} | ||
| idValue={prefixedID(id, 'dedicated-resources')} | ||
| canEdit | ||
| onEditClick={() => setDedicatedResourcesModalOpen(true)} | ||
| editButtonId={prefixedID(id, 'dedicated-resources-edit')} | ||
| > | ||
| <DedicatedResourcesModal | ||
| vmLikeEntity={template} | ||
| isOpen={isDedicatedResourcesModalOpen} | ||
| setOpen={setDedicatedResourcesModalOpen} | ||
|
||
| /> | ||
| {isCPUPinned ? RESOURCE_PINNED : RESOURCE_NOT_PINNED} | ||
| </VMDetailsItem> | ||
|
|
||
| <VMDetailsItem title="Provision Source" idValue={prefixedID(id, 'provisioning-source')}> | ||
| <TemplateSource template={template} dataVolumeLookup={dataVolumeLookup} detailed /> | ||
| </VMDetailsItem> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import { PatchBuilder, PatchOperation } from '@console/shared/src/k8s'; | ||
| import { Patch } from '@console/internal/module/k8s'; | ||
| import { VMLikeEntityKind } from '../../../types'; | ||
| import { getVMLikePatches } from '../vm-template'; | ||
| import { | ||
| getCPU, | ||
| getResourcesRequestsCPUCount, | ||
| getResourcesLimitsCPUCount, | ||
| asVM, | ||
| } from '../../../selectors/vm'; | ||
|
|
||
| export const getDedicatedCpuPatch = ( | ||
| vmLikeEntity: VMLikeEntityKind, | ||
| dedicatedCpuPlacement: boolean, | ||
| ): Patch[] => { | ||
| const vm = asVM(vmLikeEntity); | ||
| const isCPUAvailable = !!getCPU(vm); | ||
| const patches = []; | ||
|
|
||
| if (isCPUAvailable) { | ||
| patches.push( | ||
| new PatchBuilder('/spec/template/spec/domain/cpu/dedicatedCpuPlacement') | ||
| .setOperation(PatchOperation.REPLACE) | ||
| .setValue(dedicatedCpuPlacement) | ||
| .build(), | ||
| ); | ||
| } else { | ||
| const resourcesCPU = getResourcesRequestsCPUCount(vm) || getResourcesLimitsCPUCount(vm); | ||
| patches.push( | ||
| new PatchBuilder('/spec/template/spec/domain/cpu') | ||
| .setOperation(PatchOperation.REPLACE) | ||
| .setValue(resourcesCPU ? { dedicatedCpuPlacement } : { cores: 1, dedicatedCpuPlacement }) | ||
| .build(), | ||
| ); | ||
| } | ||
|
|
||
| return getVMLikePatches(vmLikeEntity, () => patches); | ||
|
||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it seems that its better to pass
onClosewhich wont take any parameter as input as you always callsetOpen(false).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually pass the name should be
closeas defined inModalComponentProps