From 149427a89c76f50fe06d51c26a3ce53a668ae021 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Fri, 16 Aug 2019 08:25:54 +0200 Subject: [PATCH 01/16] kubevirt: Edit VM flavor The VM Detail is enriched for the Edit VM Flavor feature, implemented as a modal dialog. --- .../src/components/modals/index.ts | 2 + .../vm-description-modal.tsx | 1 + .../vm-flavor-modal/_vm-flavor-modal.scss | 18 ++ .../modals/vm-flavor-modal/index.ts | 1 + .../vm-flavor-modal/vm-flavor-modal.tsx | 167 ++++++++++++++++ .../src/components/vms/types.ts | 3 +- .../src/components/vms/vm-details-page.tsx | 9 +- .../src/components/vms/vm-details.tsx | 18 +- .../src/components/vms/vm-resource.tsx | 26 ++- .../kubevirt-plugin/src/constants/index.ts | 1 + .../src/constants/namespace.ts | 1 + .../src/k8s/patches/vm/vm-patches.ts | 182 +++++++++++++++++- .../src/selectors/vm-template/selectors.ts | 21 ++ 13 files changed, 437 insertions(+), 13 deletions(-) create mode 100644 frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/_vm-flavor-modal.scss create mode 100644 frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/index.ts create mode 100644 frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx create mode 100644 frontend/packages/kubevirt-plugin/src/constants/namespace.ts diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/index.ts b/frontend/packages/kubevirt-plugin/src/components/modals/index.ts index 6601c3928c8..31334748a83 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/index.ts +++ b/frontend/packages/kubevirt-plugin/src/components/modals/index.ts @@ -1,3 +1,5 @@ export * from './create-vm-wizard'; export * from './delete-device-modal'; export * from './modal-resource-launcher'; +export * from './vm-description-modal'; +export * from './vm-flavor-modal'; diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/vm-description-modal/vm-description-modal.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/vm-description-modal/vm-description-modal.tsx index 55476af3ac1..d35cfdee399 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/vm-description-modal/vm-description-modal.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/modals/vm-description-modal/vm-description-modal.tsx @@ -13,6 +13,7 @@ import { VMLikeEntityKind } from '../../../types'; import { getDescription, getVMLikeModel } from '../../../selectors/selectors'; import { getUpdateDescriptionPatches } from '../../../k8s/patches/vm/vm-patches'; +// TODO: should be moved under kubevirt-plugin/src/style.scss import './_vm-description-modal.scss'; export const VMDescriptionModal = withHandlePromise((props: VMDescriptionModalProps) => { diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/_vm-flavor-modal.scss b/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/_vm-flavor-modal.scss new file mode 100644 index 00000000000..954a4867e72 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/_vm-flavor-modal.scss @@ -0,0 +1,18 @@ +.kubevirt-vm-flavor-modal__content { + min-height: 13em; +} + +.kubevirt-vm-flavor-modal__form { + min-height: 10em; + resize: vertical; +} + +.kubevirt-vm-flavor-modal__dropdown { + .dropdown { + width: 100%; + } +} + +.kubevirt-vm-flavor-modal__dropdown-button { + width: 100%; +} diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/index.ts b/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/index.ts new file mode 100644 index 00000000000..36b5defc1ec --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/index.ts @@ -0,0 +1 @@ +export * from './vm-flavor-modal'; diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx new file mode 100644 index 00000000000..e631df5cc9f --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx @@ -0,0 +1,167 @@ +import * as React from 'react'; +import { getVmTemplate } from 'kubevirt-web-ui-components'; +import { + HandlePromiseProps, + withHandlePromise, + Dropdown, + convertToBaseValue, +} from '@console/internal/components/utils'; +import { + createModalLauncher, + ModalTitle, + ModalBody, + ModalComponentProps, + ModalFooter, +} from '@console/internal/components/factory'; +import { k8sPatch, TemplateKind } from '@console/internal/module/k8s'; +import { Form, FormGroup, TextInput } from '@patternfly/react-core'; +import { VMKind } from '../../../types'; +import { + getFlavor, + getMemory, + getCPU, + getOperatingSystem, + getWorkloadProfile, +} from '../../../selectors/vm'; +import { getUpdateFlavorPatches } from '../../../k8s/patches/vm/vm-patches'; +import { VirtualMachineModel } from '../../../models'; +import { CUSTOM_FLAVOR } from '../../../constants'; +import { getTemplateFlavors, getTemplates } from '../../../selectors/vm-template/selectors'; + +import './_vm-flavor-modal.scss'; + +const MB = 1000 ** 2; + +const getId = (field: string) => `vm-flavor-modal-${field}`; +const dehumanizeMemory = (memory?: string) => { + if (!memory) { + return null; + } + + return convertToBaseValue(memory) / MB; +}; + +const getFlavors = (vm: VMKind, templates: TemplateKind[]) => { + const vmTemplate = getVmTemplate(vm); + + const flavors = { + // always listed + [CUSTOM_FLAVOR]: CUSTOM_FLAVOR, + }; + + if (vmTemplate) { + // enforced by the vm + const templateFlavors = getTemplateFlavors([vmTemplate]); + templateFlavors.forEach((f) => (flavors[f] = f)); + } + + // if VM OS or Workload is set, add flavors of matching templates only. Otherwise list all flavors. + const vmOS = getOperatingSystem(vm); + const vmWorkload = getWorkloadProfile(vm); + const matchingTemplates = getTemplates(templates, vmOS, vmWorkload, undefined); + const templateFlavors = getTemplateFlavors(matchingTemplates); + templateFlavors.forEach((f) => (flavors[f] = f)); + + return flavors; +}; + +const getTemplate = (templates: TemplateKind[], vm: VMKind, flavor: string) => { + const vmOS = getOperatingSystem(vm); + const vmWorkload = getWorkloadProfile(vm); + const matchingTemplates = getTemplates(templates, vmOS, vmWorkload, flavor); + + // Take first matching. If OS/Workloads changes in the future, there will be another patch sent + return matchingTemplates.length > 0 ? matchingTemplates[0] : undefined; +}; + +export const VMFlavorModal = withHandlePromise((props: VMFlavornModalProps) => { + const { vm, templates, inProgress, errorMessage, handlePromise, close, cancel } = props; + + const vmFlavor = getFlavor(vm); + const [flavor, setFlavor] = React.useState(vmFlavor); + const [mem, setMem] = React.useState( + vmFlavor === CUSTOM_FLAVOR ? dehumanizeMemory(getMemory(vm)) : 1, + ); + const [cpu, setCpu] = React.useState(vmFlavor === CUSTOM_FLAVOR ? getCPU(vm) : 1); + + const flavors = getFlavors(vm, templates); + + const submit = (e) => { + e.preventDefault(); + + const patches = getUpdateFlavorPatches( + vm, + getTemplate(templates, vm, flavor), + flavor, + cpu, + `${mem}M`, + ); + if (patches.length === 0) { + close(); + } else { + const promise = k8sPatch(VirtualMachineModel, vm, patches); + handlePromise(promise).then(close); // eslint-disable-line promise/catch-or-return + } + }; + + return ( +
+ Edit Flavor + +
+ + setFlavor(f)} + selectedKey={flavor || CUSTOM_FLAVOR} + title={flavor} + className="kubevirt-vm-flavor-modal__dropdown" + buttonClassName="kubevirt-vm-flavor-modal__dropdown-button" + /> + + + {flavor === CUSTOM_FLAVOR && ( + + + setCpu(parseInt(v, 10) || 1)} + aria-label="CPU count" + /> + + + setMem(parseInt(v, 10) || 1)} + aria-label="Memory" + /> + + + )} +
+
+ + + + +
+ ); +}); + +export type VMFlavornModalProps = HandlePromiseProps & + ModalComponentProps & { + vm: VMKind; + templates: TemplateKind[]; + }; + +export const vmFlavorModal = createModalLauncher(VMFlavorModal); diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/types.ts b/frontend/packages/kubevirt-plugin/src/components/vms/types.ts index 6f557527563..b1210538456 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vms/types.ts +++ b/frontend/packages/kubevirt-plugin/src/components/vms/types.ts @@ -1,4 +1,4 @@ -import { K8sResourceKind, PodKind } from '@console/internal/module/k8s'; +import { K8sResourceKind, PodKind, TemplateKind } from '@console/internal/module/k8s'; import { VMIKind, VMKind } from '../../types/vm'; import { VMLikeEntityKind } from '../../types'; @@ -7,6 +7,7 @@ export type VMTabProps = { vmi?: VMIKind; pods?: PodKind[]; migrations?: K8sResourceKind[]; + templates?: TemplateKind[]; }; export type VMLikeEntityTabProps = { diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/vm-details-page.tsx b/frontend/packages/kubevirt-plugin/src/components/vms/vm-details-page.tsx index 097aa67d425..5e2d9209775 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vms/vm-details-page.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vms/vm-details-page.tsx @@ -3,10 +3,11 @@ import { getResource } from 'kubevirt-web-ui-components'; import { navFactory } from '@console/internal/components/utils'; import { DetailsPage } from '@console/internal/components/factory'; import { K8sResourceKindReference } from '@console/internal/module/k8s'; -import { PodModel } from '@console/internal/models'; +import { PodModel, TemplateModel } from '@console/internal/models'; import { VMDisksFirehose } from '../vm-disks'; import { VMNics } from '../vm-nics'; import { VirtualMachineInstanceMigrationModel, VirtualMachineInstanceModel } from '../../models'; +import { NAMESPACE_OPENSHIFT, TEMPLATE_TYPE_LABEL, TEMPLATE_TYPE_BASE } from '../../constants'; import { VMEvents } from './vm-events'; import { VMConsoleFirehose } from './vm-console'; import { VMDetailsFirehose } from './vm-details'; @@ -25,6 +26,12 @@ export const VirtualMachinesDetailsPage: React.FC = ({ obj: vm, vmi, pods, migrations }) => { +export const VMDetailsFirehose: React.FC = ({ + obj: vm, + vmi, + pods, + migrations, + templates, +}) => { const resources = [getResource(ServiceModel, { namespace: getNamespace(vm), prop: 'services' })]; return (
- +
); }; const VMDetails: React.FC = (props) => { - const { vm, vmi, pods, migrations, ...restProps } = props; + const { vm, vmi, pods, migrations, templates, ...restProps } = props; const mainResources = { vm, vmi, pods, migrations, + templates, }; const vmServicesData = getServicesForVm(getLoadedData(props.services, []), vm); @@ -54,7 +61,7 @@ const VMDetails: React.FC = (props) => {
- +
@@ -72,4 +79,5 @@ type VMDetailsProps = { migrations?: K8sResourceKind[]; vmi?: VMIKind; services?: FirehoseResult; + templates?: TemplateKind[]; }; diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/vm-resource.tsx b/frontend/packages/kubevirt-plugin/src/components/vms/vm-resource.tsx index 0e8596adca6..62fe69af4bf 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vms/vm-resource.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vms/vm-resource.tsx @@ -13,13 +13,13 @@ import { getBootableDevicesInOrder, } from 'kubevirt-web-ui-components'; import { ResourceSummary, NodeLink, ResourceLink } from '@console/internal/components/utils'; -import { PodKind } from '@console/internal/module/k8s'; +import { PodKind, TemplateKind } from '@console/internal/module/k8s'; import { getName, getNamespace, DASH } from '@console/shared'; import { PodModel } from '@console/internal/models'; import { VMKind, VMIKind } from '../../types'; import { VMTemplateLink } from '../vm-templates/vm-template-link'; import { getBasicID, prefixedID } from '../../utils'; -import { vmDescriptionModal } from '../modals/vm-description-modal'; +import { vmDescriptionModal, vmFlavorModal } from '../modals'; import { getDescription } from '../../selectors/selectors'; import { getVMStatus } from '../../statuses/vm/vm'; @@ -58,7 +58,14 @@ export const VMResourceSummary: React.FC = ({ vm, canUpd ); }; -export const VMDetailsList: React.FC = ({ vm, vmi, pods, migrations }) => { +export const VMDetailsList: React.FC = ({ + vm, + vmi, + pods, + migrations, + templates, + canUpdateVM, +}) => { const id = getBasicID(vm); const vmStatus = getVMStatus(vm, pods, migrations); const { launcherPod } = vmStatus; @@ -99,7 +106,16 @@ export const VMDetailsList: React.FC = ({ vm, vmi, pods, mi
Node
{}
-
Flavor
+
+ Flavor + {canUpdateVM && ( +
{getFlavor(vm) || DASH}
Workload Profile
{getWorkloadProfile(vm) || DASH}
@@ -117,4 +133,6 @@ type VMResourceListProps = { pods?: PodKind[]; migrations?: any[]; vmi?: VMIKind; + templates?: TemplateKind[]; + canUpdateVM: boolean; }; diff --git a/frontend/packages/kubevirt-plugin/src/constants/index.ts b/frontend/packages/kubevirt-plugin/src/constants/index.ts index 034fabedd65..772837351d0 100644 --- a/frontend/packages/kubevirt-plugin/src/constants/index.ts +++ b/frontend/packages/kubevirt-plugin/src/constants/index.ts @@ -1,3 +1,4 @@ export * from './vm'; export * from './vm-templates'; export * from './cdi'; +export * from './namespace'; diff --git a/frontend/packages/kubevirt-plugin/src/constants/namespace.ts b/frontend/packages/kubevirt-plugin/src/constants/namespace.ts new file mode 100644 index 00000000000..37e37fa28d8 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/constants/namespace.ts @@ -0,0 +1 @@ +export const NAMESPACE_OPENSHIFT = 'openshift'; diff --git a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-patches.ts b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-patches.ts index 6356cdffaa8..0353104cf3c 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-patches.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-patches.ts @@ -1,6 +1,161 @@ -import { Patch } from '@console/internal/module/k8s'; -import { VMLikeEntityKind } from '../../../types'; +import * as _ from 'lodash'; +import { Patch, TemplateKind } from '@console/internal/module/k8s'; +import { VMLikeEntityKind, VMKind } from '../../../types'; import { getAnnotations, getDescription } from '../../../selectors/selectors'; +import { getFlavor, getCPU, getMemory } from '../../../selectors/vm'; +import { CUSTOM_FLAVOR, TEMPLATE_FLAVOR_LABEL } from '../../../constants'; +import { selectVM } from '../../../selectors/vm-template/selectors'; + +const getLabelsPatch = (vm: VMKind): Patch | null => { + if (!_.has(vm.metadata, 'labels')) { + return { + op: 'add', + path: '/metadata/labels', + value: {}, + }; + } + return null; +}; + +const getDomainPatch = (vm: VMKind): Patch | null => { + let patch: Patch = null; + if (!_.has(vm, 'spec')) { + patch = { + op: 'add', + path: '/spec', + value: { + template: { + spec: { + domain: {}, + }, + }, + }, + }; + } else if (!_.has(vm.spec, 'template')) { + patch = { + op: 'add', + path: '/spec/template', + value: { + spec: { + domain: {}, + }, + }, + }; + } else if (!_.has(vm.spec.template, 'spec')) { + patch = { + op: 'add', + path: '/spec/template/spec', + value: { + domain: {}, + }, + }; + } else if (!_.has(vm.spec.template.spec, 'domain')) { + patch = { + op: 'add', + path: '/spec/template/spec/domain', + value: {}, + }; + } + return patch; +}; + +const getUpdateFlavorPatch = (vm, flavor): Patch[] => { + const patch = []; + if (flavor !== getFlavor(vm)) { + const labelKey = `${TEMPLATE_FLAVOR_LABEL}/${flavor}`.replace('~', '~0').replace('/', '~1'); + const labelPatch = getLabelsPatch(vm); + if (labelPatch) { + patch.push(labelPatch); + } + const flavorLabel = Object.keys(vm.metadata.labels || {}).find((key) => + key.startsWith(TEMPLATE_FLAVOR_LABEL), + ); + if (flavorLabel) { + const flavorParts = flavorLabel.split('/'); + if (flavorParts[flavorParts.length - 1] !== flavor) { + const escapedLabel = flavorLabel.replace('~', '~0').replace('/', '~1'); + patch.push({ + op: 'remove', + path: `/metadata/labels/${escapedLabel}`, + }); + } + } + patch.push({ + op: 'add', + path: `/metadata/labels/${labelKey}`, + value: 'true', + }); + } + return patch; +}; + +const getCpuPatch = (vm: VMKind, cpu: string): Patch => { + if (!_.has(vm.spec, 'template.spec.domain.cpu')) { + return { + op: 'add', + path: '/spec/template/spec/domain/cpu', + value: { + cores: parseInt(cpu, 10), + }, + }; + } + return { + op: _.has(vm.spec, 'template.spec.domain.cpu.cores') ? 'replace' : 'add', + path: '/spec/template/spec/domain/cpu/cores', + value: parseInt(cpu, 10), + }; +}; + +const getMemoryPatch = (vm: VMKind, memory: string): Patch => { + if (!_.has(vm.spec, 'template.spec.domain.resources')) { + return { + op: 'add', + path: '/spec/template/spec/domain/resources', + value: { + requests: { + memory, + }, + }, + }; + } + if (!_.has(vm.spec, 'template.spec.domain.resources.requests')) { + return { + op: 'add', + path: '/spec/template/spec/domain/resources/requests', + value: { + memory, + }, + }; + } + return { + op: _.has(vm.spec, 'template.spec.domain.resources.requests.memory') ? 'replace' : 'add', + path: '/spec/template/spec/domain/resources/requests/memory', + value: memory, + }; +}; + +const getUpdateCpuMemoryPatch = (vm: VMKind, cpu: string, memory: string): Patch[] => { + const patch = []; + const vmCpu = getCPU(vm); + const vmMemory = getMemory(vm); + + if (parseInt(cpu, 10) !== vmCpu || memory !== vmMemory) { + const domainPatch = getDomainPatch(vm); + if (domainPatch) { + patch.push(domainPatch); + } + } + + if (parseInt(cpu, 10) !== vmCpu) { + patch.push(getCpuPatch(vm, cpu)); + } + + if (memory !== vmMemory) { + patch.push(getMemoryPatch(vm, memory)); + } + + return patch; +}; export const getUpdateDescriptionPatches = ( vmLikeEntity: VMLikeEntityKind, @@ -34,3 +189,26 @@ export const getUpdateDescriptionPatches = ( } return patches; }; + +export const getUpdateFlavorPatches = ( + vm: VMKind, + template: TemplateKind, + flavor: string, + cpu?: string, + mem?: string, +): Patch[] => { + const patches = []; + + patches.push(...getUpdateFlavorPatch(vm, flavor)); + + let customCpu = cpu; + let customMem = mem; + if (flavor !== CUSTOM_FLAVOR) { + const templateVm = selectVM(template); + customCpu = getCPU(templateVm); + customMem = getMemory(templateVm); + } + patches.push(...getUpdateCpuMemoryPatch(vm, customCpu, customMem)); + + return patches; +}; diff --git a/frontend/packages/kubevirt-plugin/src/selectors/vm-template/selectors.ts b/frontend/packages/kubevirt-plugin/src/selectors/vm-template/selectors.ts index 1daae3c1347..7243bee31b1 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/vm-template/selectors.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/vm-template/selectors.ts @@ -2,6 +2,27 @@ import * as _ from 'lodash'; import { TemplateKind } from '@console/internal/module/k8s'; import { VirtualMachineModel } from '../../models'; import { VMKind } from '../../types'; +import { TEMPLATE_FLAVOR_LABEL } from '../../constants'; +import { getLabels } from '../selectors'; export const selectVM = (vmTemplate: TemplateKind): VMKind => _.get(vmTemplate, 'objects', []).find((obj) => obj.kind === VirtualMachineModel.kind); + +export const getTemplatesLabelValues = (templates: TemplateKind[], label: string) => { + const labelValues = []; + (templates || []).forEach(t => { + const labels = Object.keys(getLabels(t, [])).filter(l => l.startsWith(label)); + labels.forEach(l => { + const labelParts = l.split('/'); + if (labelParts.length > 1) { + const labelName = labelParts[labelParts.length - 1]; + if (labelValues.indexOf(labelName) === -1) { + labelValues.push(labelName); + } + } + }); + }); + return labelValues; +}; + +export const getTemplateFlavors = (vmTemplates: TemplateKind[]) => getTemplatesLabelValues(vmTemplates, TEMPLATE_FLAVOR_LABEL); From 5e345ebf34e3b45bef29a448e096676dd9103729 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Fri, 23 Aug 2019 08:05:51 +0200 Subject: [PATCH 02/16] kubevirt: Edit Template flavor --- .../vm-flavor-modal/vm-flavor-modal.tsx | 93 ++++++++++++------- .../vm-templates/vm-template-details.tsx | 8 +- .../vm-templates/vm-template-resource.tsx | 15 ++- .../src/components/vms/vm-details-page.tsx | 9 +- .../src/components/vms/vm-resource.tsx | 6 +- .../src/k8s/patches/vm/vm-patches.ts | 87 ++++++++++------- .../src/selectors/vm-template/selectors.ts | 56 ++++++++++- .../src/selectors/vm/selectors.ts | 11 ++- .../kubevirt-plugin/src/types/vm/index.ts | 6 ++ .../kubevirt-plugin/src/utils/index.ts | 4 + 10 files changed, 205 insertions(+), 90 deletions(-) diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx index e631df5cc9f..82995477bab 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx @@ -1,11 +1,14 @@ import * as React from 'react'; -import { getVmTemplate } from 'kubevirt-web-ui-components'; +import * as _ from 'lodash'; +import { getVmTemplate, getResource } from 'kubevirt-web-ui-components'; import { HandlePromiseProps, withHandlePromise, Dropdown, convertToBaseValue, + Firehose, } from '@console/internal/components/utils'; +import { TemplateModel } from '@console/internal/models'; import { createModalLauncher, ModalTitle, @@ -15,7 +18,7 @@ import { } from '@console/internal/components/factory'; import { k8sPatch, TemplateKind } from '@console/internal/module/k8s'; import { Form, FormGroup, TextInput } from '@patternfly/react-core'; -import { VMKind } from '../../../types'; +import { VMKind, VMLikeEntityKind } from '../../../types'; import { getFlavor, getMemory, @@ -23,11 +26,20 @@ import { getOperatingSystem, getWorkloadProfile, } from '../../../selectors/vm'; +import { isVM } from '../../../selectors/selectors'; import { getUpdateFlavorPatches } from '../../../k8s/patches/vm/vm-patches'; import { VirtualMachineModel } from '../../../models'; -import { CUSTOM_FLAVOR } from '../../../constants'; -import { getTemplateFlavors, getTemplates } from '../../../selectors/vm-template/selectors'; - +import { + CUSTOM_FLAVOR, + NAMESPACE_OPENSHIFT, + TEMPLATE_TYPE_LABEL, + TEMPLATE_TYPE_BASE, +} from '../../../constants'; +import { + getTemplateFlavors, + getTemplates, + selectVM, +} from '../../../selectors/vm-template/selectors'; import './_vm-flavor-modal.scss'; const MB = 1000 ** 2; @@ -41,7 +53,7 @@ const dehumanizeMemory = (memory?: string) => { return convertToBaseValue(memory) / MB; }; -const getFlavors = (vm: VMKind, templates: TemplateKind[]) => { +const getFlavors = (vm: VMLikeEntityKind, templates: TemplateKind[]) => { const vmTemplate = getVmTemplate(vm); const flavors = { @@ -65,41 +77,44 @@ const getFlavors = (vm: VMKind, templates: TemplateKind[]) => { return flavors; }; -const getTemplate = (templates: TemplateKind[], vm: VMKind, flavor: string) => { - const vmOS = getOperatingSystem(vm); - const vmWorkload = getWorkloadProfile(vm); - const matchingTemplates = getTemplates(templates, vmOS, vmWorkload, flavor); +const VMFlavorModal = withHandlePromise((props: VMFlavornModalProps) => { + const { vmLike, templates, inProgress, errorMessage, handlePromise, close, cancel } = props; - // Take first matching. If OS/Workloads changes in the future, there will be another patch sent - return matchingTemplates.length > 0 ? matchingTemplates[0] : undefined; -}; + const flattenTemplates = _.get(templates, 'data', []) as TemplateKind[]; -export const VMFlavorModal = withHandlePromise((props: VMFlavornModalProps) => { - const { vm, templates, inProgress, errorMessage, handlePromise, close, cancel } = props; + const vmFlavor = getFlavor(vmLike); + const flavors = getFlavors(vmLike, flattenTemplates); + + const sourceMemory = isVM(vmLike) + ? getMemory(vmLike as VMKind) + : getMemory(selectVM(vmLike as TemplateKind)); + + const sourceCPURaw = isVM(vmLike) + ? getCPU(vmLike as VMKind) + : getCPU(selectVM(vmLike as TemplateKind)); + let sourceCPU = parseInt(sourceCPURaw, 10); + if (!sourceCPU) { + sourceCPU = + (parseInt(sourceCPURaw.sockets, 10) || 1) * + (parseInt(sourceCPURaw.cores, 10) || 1) * + (parseInt(sourceCPURaw.threads, 10) || 1); + } - const vmFlavor = getFlavor(vm); const [flavor, setFlavor] = React.useState(vmFlavor); const [mem, setMem] = React.useState( - vmFlavor === CUSTOM_FLAVOR ? dehumanizeMemory(getMemory(vm)) : 1, + vmFlavor === CUSTOM_FLAVOR ? dehumanizeMemory(sourceMemory) : 1, ); - const [cpu, setCpu] = React.useState(vmFlavor === CUSTOM_FLAVOR ? getCPU(vm) : 1); - - const flavors = getFlavors(vm, templates); + const [cpu, setCpu] = React.useState(vmFlavor === CUSTOM_FLAVOR ? sourceCPU : 1); const submit = (e) => { e.preventDefault(); - const patches = getUpdateFlavorPatches( - vm, - getTemplate(templates, vm, flavor), - flavor, - cpu, - `${mem}M`, - ); + const patches = getUpdateFlavorPatches(vmLike, flattenTemplates, flavor, cpu, `${mem}M`); if (patches.length === 0) { close(); } else { - const promise = k8sPatch(VirtualMachineModel, vm, patches); + const model = isVM(vmLike) ? VirtualMachineModel : TemplateModel; + const promise = k8sPatch(model, vmLike, patches); handlePromise(promise).then(close); // eslint-disable-line promise/catch-or-return } }; @@ -158,10 +173,26 @@ export const VMFlavorModal = withHandlePromise((props: VMFlavornModalProps) => { ); }); +const VMFlavorModalFirehose = (props) => { + const resources = [ + getResource(TemplateModel, { + namespace: NAMESPACE_OPENSHIFT, + prop: 'templates', + matchLabels: { [TEMPLATE_TYPE_LABEL]: TEMPLATE_TYPE_BASE }, + }), + ]; + + return ( + + + + ); +}; + export type VMFlavornModalProps = HandlePromiseProps & ModalComponentProps & { - vm: VMKind; - templates: TemplateKind[]; + vmLike: VMLikeEntityKind; + templates?: any; }; -export const vmFlavorModal = createModalLauncher(VMFlavorModal); +export const vmFlavorModal = createModalLauncher(VMFlavorModalFirehose); diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-templates/vm-template-details.tsx b/frontend/packages/kubevirt-plugin/src/components/vm-templates/vm-template-details.tsx index a6839183fee..011870ac271 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vm-templates/vm-template-details.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vm-templates/vm-template-details.tsx @@ -24,14 +24,16 @@ const VMTemplateDetailsFirehose: React.FC = (pro getResource(DataVolumeModel, { namespace, optional: true, prop: 'datavolumes' }), ]; + const otherProps = { template }; + return (
{hasDataVolumes ? ( - + ) : ( - + )}
); @@ -65,7 +67,7 @@ const VMTemplateDetails: React.FC = (props) => {
- +
diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-templates/vm-template-resource.tsx b/frontend/packages/kubevirt-plugin/src/components/vm-templates/vm-template-resource.tsx index f223516a27d..2ea715962dc 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vm-templates/vm-template-resource.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vm-templates/vm-template-resource.tsx @@ -15,8 +15,8 @@ import { TemplateKind, K8sResourceKind } from '@console/internal/module/k8s'; import { getBasicID, prefixedID } from '../../utils'; import { vmDescriptionModal } from '../modals/vm-description-modal'; import { getDescription } from '../../selectors/selectors'; +import { vmFlavorModal } from '../modals'; import { VMTemplateLink } from './vm-template-link'; - import './_vm-template-resource.scss'; export const VMTemplateResourceSummary: React.FC = ({ @@ -60,6 +60,7 @@ export const VMTemplateResourceSummary: React.FC export const VMTemplateDetailsList: React.FC = ({ template, dataVolumes, + canUpdateTemplate, }) => { const id = getBasicID(template); const sortedBootableDevices = getBootableDevicesInOrder(template); @@ -74,7 +75,16 @@ export const VMTemplateDetailsList: React.FC = ({ DASH )} -
Flavor
+
+ Flavor + {canUpdateTemplate && ( +
{getFlavor(template) || DASH}
Provision Source
@@ -91,6 +101,7 @@ export const VMTemplateDetailsList: React.FC = ({ type VMTemplateResourceListProps = { template: TemplateKind; dataVolumes: K8sResourceKind[]; + canUpdateTemplate: boolean; }; type VMTemplateResourceSummaryProps = { diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/vm-details-page.tsx b/frontend/packages/kubevirt-plugin/src/components/vms/vm-details-page.tsx index 5e2d9209775..097aa67d425 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vms/vm-details-page.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vms/vm-details-page.tsx @@ -3,11 +3,10 @@ import { getResource } from 'kubevirt-web-ui-components'; import { navFactory } from '@console/internal/components/utils'; import { DetailsPage } from '@console/internal/components/factory'; import { K8sResourceKindReference } from '@console/internal/module/k8s'; -import { PodModel, TemplateModel } from '@console/internal/models'; +import { PodModel } from '@console/internal/models'; import { VMDisksFirehose } from '../vm-disks'; import { VMNics } from '../vm-nics'; import { VirtualMachineInstanceMigrationModel, VirtualMachineInstanceModel } from '../../models'; -import { NAMESPACE_OPENSHIFT, TEMPLATE_TYPE_LABEL, TEMPLATE_TYPE_BASE } from '../../constants'; import { VMEvents } from './vm-events'; import { VMConsoleFirehose } from './vm-console'; import { VMDetailsFirehose } from './vm-details'; @@ -26,12 +25,6 @@ export const VirtualMachinesDetailsPage: React.FC = ({ vmi, pods, migrations, - templates, canUpdateVM, }) => { const id = getBasicID(vm); @@ -112,7 +111,7 @@ export const VMDetailsList: React.FC = ({ @@ -162,6 +175,8 @@ export type VMFlavornModalProps = HandlePromiseProps & ModalComponentProps & { vmLike: VMLikeEntityKind; templates?: any; + loadError?: any; + loaded: boolean; }; export const vmFlavorModal = createModalLauncher(VMFlavorModalFirehose); From d5666a93e0191c960a9795893c2901fe29fd75c1 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Wed, 28 Aug 2019 14:09:45 +0200 Subject: [PATCH 09/16] kubevirt: VM Template Detail shows memory and CPU Part of the Flavor field. --- .../src/components/flavor-text.tsx | 17 +++++++++++++++++ .../vm-templates/vm-template-resource.tsx | 7 +++++-- .../src/components/vms/vm-resource.tsx | 11 ++++------- 3 files changed, 26 insertions(+), 9 deletions(-) create mode 100644 frontend/packages/kubevirt-plugin/src/components/flavor-text.tsx diff --git a/frontend/packages/kubevirt-plugin/src/components/flavor-text.tsx b/frontend/packages/kubevirt-plugin/src/components/flavor-text.tsx new file mode 100644 index 00000000000..8eeacc0ead7 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/flavor-text.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import { getFlavor, vCPUCount, getCPU, getMemory, asVM } from '../selectors/vm'; +import { VMLikeEntityKind } from '../types'; + +export const FlavorText: React.FC = (props) => { + const { vmLike } = props; + const vm = asVM(vmLike); + + const flavor = getFlavor(vmLike); + const vcpusCount = vCPUCount(getCPU(vm)); + const vcpusText = `${vcpusCount} vCPU${vcpusCount > 1 ? 's' : ''}`; + return <>{`${flavor || ''}${flavor ? ', ' : ''}${vcpusText}, ${getMemory(vm)} Memory`}; +}; + +type FlavorTextProps = { + vmLike: VMLikeEntityKind; +}; diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-templates/vm-template-resource.tsx b/frontend/packages/kubevirt-plugin/src/components/vm-templates/vm-template-resource.tsx index 2ea715962dc..2dfac875587 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vm-templates/vm-template-resource.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vm-templates/vm-template-resource.tsx @@ -4,7 +4,6 @@ import { getOperatingSystem, getWorkloadProfile, getVmTemplate, - getFlavor, BootOrder, getBootableDevicesInOrder, TemplateSource, @@ -16,7 +15,9 @@ import { getBasicID, prefixedID } from '../../utils'; import { vmDescriptionModal } from '../modals/vm-description-modal'; import { getDescription } from '../../selectors/selectors'; import { vmFlavorModal } from '../modals'; +import { FlavorText } from '../flavor-text'; import { VMTemplateLink } from './vm-template-link'; + import './_vm-template-resource.scss'; export const VMTemplateResourceSummary: React.FC = ({ @@ -85,7 +86,9 @@ export const VMTemplateDetailsList: React.FC = ({ /> )} -
{getFlavor(template) || DASH}
+
+ +
Provision Source
{dataVolumes ? ( diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/vm-resource.tsx b/frontend/packages/kubevirt-plugin/src/components/vms/vm-resource.tsx index c62ff7a8c67..e392c6b3230 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vms/vm-resource.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vms/vm-resource.tsx @@ -6,7 +6,6 @@ import { getWorkloadProfile, getVmTemplate, getNodeName, - getFlavor, VmStatuses, BootOrder, isVmOff, @@ -21,8 +20,8 @@ import { VMTemplateLink } from '../vm-templates/vm-template-link'; import { getBasicID, prefixedID } from '../../utils'; import { vmDescriptionModal, vmFlavorModal } from '../modals'; import { getDescription } from '../../selectors/selectors'; -import { getCPU, getMemory, vCPUCount } from '../../selectors/vm'; import { getVMStatus } from '../../statuses/vm/vm'; +import { FlavorText } from '../flavor-text'; import './_vm-resource.scss'; @@ -74,10 +73,6 @@ export const VMDetailsList: React.FC = ({ const vmIsOff = isVmOff(vmStatus); const ipAddresses = vmIsOff ? [] : getVmiIpAddresses(vmi); - const flavor = getFlavor(vm); - const vcpusCount = vCPUCount(getCPU(vm)); - const vcpusText = `${vcpusCount} vCPU${vcpusCount > 1 ? 's' : ''}`; - const flavorText = `${flavor || ''}${flavor ? ', ' : ''}${vcpusText}, ${getMemory(vm)} Memory`; return (
Status
@@ -120,7 +115,9 @@ export const VMDetailsList: React.FC = ({ /> )} -
{flavorText}
+
+ +
Workload Profile
{getWorkloadProfile(vm) || DASH}
From 36facf0925408861cddc9108fce43d7eb3baece6 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Wed, 28 Aug 2019 14:18:32 +0200 Subject: [PATCH 10/16] edit vm flavor: use classNames --- .../modals/vm-flavor-modal/vm-flavor-modal.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx index 1c2f7eb6069..84b2c7df69e 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import * as _ from 'lodash'; +import * as classNames from 'classnames'; import { getResource } from 'kubevirt-web-ui-components'; import { HandlePromiseProps, @@ -91,11 +92,10 @@ const VMFlavorModal = withHandlePromise((props: VMFlavornModalProps) => { } }; - let topClass = 'modal-content '; - topClass += - flavor === CUSTOM_FLAVOR - ? 'kubevirt-vm-flavor-modal__content-custom' - : 'kubevirt-vm-flavor-modal__content-generic'; + const topClass = classNames('modal-content', { + 'kubevirt-vm-flavor-modal__content-custom': flavor === CUSTOM_FLAVOR, + 'kubevirt-vm-flavor-modal__content-generic': flavor !== CUSTOM_FLAVOR, + }); return (
From 1c4b36e5877c161a8b297faefaac651dfeb92d9c Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Wed, 28 Aug 2019 14:33:27 +0200 Subject: [PATCH 11/16] Introduce EditButton for future reuse Refactoring only. --- .../src/components/edit-button.tsx | 23 +++++++++++++++++++ .../vm-templates/vm-template-resource.tsx | 12 ++++------ .../src/components/vms/vm-resource.tsx | 9 ++------ 3 files changed, 30 insertions(+), 14 deletions(-) create mode 100644 frontend/packages/kubevirt-plugin/src/components/edit-button.tsx diff --git a/frontend/packages/kubevirt-plugin/src/components/edit-button.tsx b/frontend/packages/kubevirt-plugin/src/components/edit-button.tsx new file mode 100644 index 00000000000..48d9c72b3b4 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/edit-button.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import { Button } from '@patternfly/react-core'; + +export const EditButton: React.FC = (props) => { + const { canEdit, onClick } = props; + + if (canEdit) { + return ( +
Flavor - {canUpdateTemplate && ( -
diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/vm-resource.tsx b/frontend/packages/kubevirt-plugin/src/components/vms/vm-resource.tsx index e392c6b3230..18569fcce0f 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vms/vm-resource.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vms/vm-resource.tsx @@ -22,6 +22,7 @@ import { vmDescriptionModal, vmFlavorModal } from '../modals'; import { getDescription } from '../../selectors/selectors'; import { getVMStatus } from '../../statuses/vm/vm'; import { FlavorText } from '../flavor-text'; +import { EditButton } from '../edit-button'; import './_vm-resource.scss'; @@ -107,13 +108,7 @@ export const VMDetailsList: React.FC = ({
{}
Flavor - {canUpdateVM && ( -
From 40be3196d51e8433d69f1588fc0f5aae68e76d1a Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Wed, 28 Aug 2019 14:58:48 +0200 Subject: [PATCH 12/16] remove typescript's null --- .../packages/kubevirt-plugin/src/k8s/patches/vm/vm-patches.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-patches.ts b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-patches.ts index c4a682d764e..1ad7e4af946 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-patches.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-patches.ts @@ -8,7 +8,7 @@ import { selectVM, getTemplateForFlavor } from '../../../selectors/vm-template/s import { getVMLikePatches } from '../vm-template'; import { isCPUEqual } from '../../../utils'; -const getLabelsPatch = (vmLike: VMLikeEntityKind): Patch | null => { +const getLabelsPatch = (vmLike: VMLikeEntityKind): Patch => { if (!_.has(vmLike.metadata, 'labels')) { return { op: 'add', @@ -19,7 +19,7 @@ const getLabelsPatch = (vmLike: VMLikeEntityKind): Patch | null => { return null; }; -const getDomainPatch = (vm: VMKind): Patch | null => { +const getDomainPatch = (vm: VMKind): Patch => { let patch: Patch = null; if (!_.has(vm, 'spec')) { patch = { From 816feb6580845b7869994952a89632bac17a8cb3 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Wed, 28 Aug 2019 15:08:27 +0200 Subject: [PATCH 13/16] Add more types --- .../kubevirt-plugin/src/k8s/patches/vm/vm-patches.ts | 2 +- .../src/selectors/vm-template/selectors.ts | 4 ++-- .../packages/kubevirt-plugin/src/selectors/vm/cpu.ts | 4 ++-- .../packages/kubevirt-plugin/src/selectors/vm/vmlike.ts | 9 +++------ 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-patches.ts b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-patches.ts index 1ad7e4af946..04079b9e188 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-patches.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-patches.ts @@ -61,7 +61,7 @@ const getDomainPatch = (vm: VMKind): Patch => { return patch; }; -const getUpdateFlavorPatch = (vmLike, flavor): Patch[] => { +const getUpdateFlavorPatch = (vmLike: VMLikeEntityKind, flavor: string): Patch[] => { const patch = []; if (flavor !== getFlavor(vmLike)) { const labelKey = `${TEMPLATE_FLAVOR_LABEL}/${flavor}`.replace('~', '~0').replace('/', '~1'); diff --git a/frontend/packages/kubevirt-plugin/src/selectors/vm-template/selectors.ts b/frontend/packages/kubevirt-plugin/src/selectors/vm-template/selectors.ts index 5fe112fb6a0..dd2763740af 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/vm-template/selectors.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/vm-template/selectors.ts @@ -40,12 +40,12 @@ export const getTemplateWorkloads = (vmTemplates: TemplateKind[]) => getTemplatesLabelValues(vmTemplates, TEMPLATE_WORKLOAD_LABEL); export const getTemplates = ( - templates: TemplateKind[], + templates: TemplateKind[] = [], os: string, workload: string, flavor: string, ) => - (templates || []).filter((t) => { + templates.filter((t) => { if (os) { const templateOS = getTemplateOS([t]); if (!templateOS.includes(os)) { diff --git a/frontend/packages/kubevirt-plugin/src/selectors/vm/cpu.ts b/frontend/packages/kubevirt-plugin/src/selectors/vm/cpu.ts index 03e18bb6f3f..4347135c0be 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/vm/cpu.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/vm/cpu.ts @@ -1,4 +1,4 @@ -export const vCPUCount = (sourceCPURaw: CPUType) => { +export const vCPUCount = (sourceCPURaw: CPURaw) => { return ( (parseInt(sourceCPURaw.sockets, 10) || 1) * (parseInt(sourceCPURaw.cores, 10) || 1) * @@ -6,7 +6,7 @@ export const vCPUCount = (sourceCPURaw: CPUType) => { ); }; -type CPUType = { +type CPURaw = { sockets: string; cores: string; threads: string; diff --git a/frontend/packages/kubevirt-plugin/src/selectors/vm/vmlike.ts b/frontend/packages/kubevirt-plugin/src/selectors/vm/vmlike.ts index 956053c1ad8..16e0b3267dd 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/vm/vmlike.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/vm/vmlike.ts @@ -1,10 +1,10 @@ -import { K8sKind, TemplateKind } from '@console/internal/module/k8s'; +import { K8sKind } from '@console/internal/module/k8s'; import { TemplateModel } from '@console/internal/models'; import { VMLikeEntityKind, VMKind } from '../../types'; import { VirtualMachineModel } from '../../models'; import { selectVM } from '../vm-template/selectors'; -export const isVM = (vmLikeEntity: VMLikeEntityKind): boolean => +export const isVM = (vmLikeEntity: VMLikeEntityKind): vmLikeEntity is VMKind => vmLikeEntity && vmLikeEntity.kind === VirtualMachineModel.kind; export const getVMLikeModel = (vmLikeEntity: VMLikeEntityKind): K8sKind => @@ -15,8 +15,5 @@ export const asVM = (vmLikeEntity: VMLikeEntityKind): VMKind => { return null; } - if (isVM(vmLikeEntity)) { - return vmLikeEntity as VMKind; - } - return selectVM(vmLikeEntity as TemplateKind); + return isVM(vmLikeEntity) ? vmLikeEntity : selectVM(vmLikeEntity); }; From 4b08bd50e764be6abde0032f6e9039a49b06eee9 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Wed, 28 Aug 2019 15:34:08 +0200 Subject: [PATCH 14/16] kubevirt: Capitalize flavors when rendering --- .../packages/kubevirt-plugin/src/components/flavor-text.tsx | 5 +++-- .../components/modals/vm-flavor-modal/vm-flavor-modal.tsx | 5 +++-- .../kubevirt-plugin/src/selectors/vm-template/selectors.ts | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/packages/kubevirt-plugin/src/components/flavor-text.tsx b/frontend/packages/kubevirt-plugin/src/components/flavor-text.tsx index 8eeacc0ead7..4b4e40f0a2a 100644 --- a/frontend/packages/kubevirt-plugin/src/components/flavor-text.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/flavor-text.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import * as _ from 'lodash'; import { getFlavor, vCPUCount, getCPU, getMemory, asVM } from '../selectors/vm'; import { VMLikeEntityKind } from '../types'; @@ -6,10 +7,10 @@ export const FlavorText: React.FC = (props) => { const { vmLike } = props; const vm = asVM(vmLike); - const flavor = getFlavor(vmLike); + const flavor = _.capitalize(getFlavor(vmLike)); const vcpusCount = vCPUCount(getCPU(vm)); const vcpusText = `${vcpusCount} vCPU${vcpusCount > 1 ? 's' : ''}`; - return <>{`${flavor || ''}${flavor ? ', ' : ''}${vcpusText}, ${getMemory(vm)} Memory`}; + return <>{`${flavor || ''}${flavor ? ': ' : ''}${vcpusText}, ${getMemory(vm)} Memory`}; }; type FlavorTextProps = { diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx index 84b2c7df69e..5c560822821 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx @@ -97,6 +97,7 @@ const VMFlavorModal = withHandlePromise((props: VMFlavornModalProps) => { 'kubevirt-vm-flavor-modal__content-generic': flavor !== CUSTOM_FLAVOR, }); + console.log('--- flavors: ', flavors); return (
Edit Flavor @@ -107,8 +108,8 @@ const VMFlavorModal = withHandlePromise((props: VMFlavornModalProps) => { items={flavors} id={getId('flavor-dropdown')} onChange={(f) => setFlavor(f)} - selectedKey={flavor || CUSTOM_FLAVOR} - title={flavor} + selectedKey={_.capitalize(flavor) || CUSTOM_FLAVOR} + title={_.capitalize(flavor)} className="kubevirt-vm-flavor-modal__dropdown" buttonClassName="kubevirt-vm-flavor-modal__dropdown-button" /> diff --git a/frontend/packages/kubevirt-plugin/src/selectors/vm-template/selectors.ts b/frontend/packages/kubevirt-plugin/src/selectors/vm-template/selectors.ts index dd2763740af..1218010361c 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/vm-template/selectors.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/vm-template/selectors.ts @@ -90,7 +90,7 @@ export const getFlavors = (vm: VMLikeEntityKind, templates: TemplateKind[]) => { if (vmTemplate) { // enforced by the vm const templateFlavors = getTemplateFlavors([vmTemplate]); - templateFlavors.forEach((f) => (flavors[f] = f)); + templateFlavors.forEach((f) => (flavors[f] = _.capitalize(f))); } // if VM OS or Workload is set, add flavors of matching templates only. Otherwise list all flavors. @@ -98,7 +98,7 @@ export const getFlavors = (vm: VMLikeEntityKind, templates: TemplateKind[]) => { const vmWorkload = getWorkloadProfile(vm); const matchingTemplates = getTemplates(templates, vmOS, vmWorkload, undefined); const templateFlavors = getTemplateFlavors(matchingTemplates); - templateFlavors.forEach((f) => (flavors[f] = f)); + templateFlavors.forEach((f) => (flavors[f] = _.capitalize(f))); return flavors; }; From 0c28e9d95f7978c8939e9bd5177a12bf26c6bbd9 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Wed, 28 Aug 2019 15:54:56 +0200 Subject: [PATCH 15/16] VM flavor: humanize memory --- .../src/components/flavor-text.tsx | 8 ++++- .../vm-flavor-modal/vm-flavor-modal.tsx | 6 +--- .../src/k8s/patches/vm/vm-patches.ts | 6 ++-- .../kubevirt-plugin/src/selectors/vm/cpu.ts | 31 +++++++++++++------ .../src/selectors/vm/selectors.ts | 4 +-- .../kubevirt-plugin/src/types/vm/index.ts | 8 +++++ 6 files changed, 42 insertions(+), 21 deletions(-) diff --git a/frontend/packages/kubevirt-plugin/src/components/flavor-text.tsx b/frontend/packages/kubevirt-plugin/src/components/flavor-text.tsx index 4b4e40f0a2a..a27149fedb1 100644 --- a/frontend/packages/kubevirt-plugin/src/components/flavor-text.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/flavor-text.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import * as _ from 'lodash'; +import { convertToBaseValue, humanizeDecimalBytes } from '@console/internal/components/utils'; import { getFlavor, vCPUCount, getCPU, getMemory, asVM } from '../selectors/vm'; import { VMLikeEntityKind } from '../types'; @@ -8,9 +9,14 @@ export const FlavorText: React.FC = (props) => { const vm = asVM(vmLike); const flavor = _.capitalize(getFlavor(vmLike)); + const vcpusCount = vCPUCount(getCPU(vm)); const vcpusText = `${vcpusCount} vCPU${vcpusCount > 1 ? 's' : ''}`; - return <>{`${flavor || ''}${flavor ? ': ' : ''}${vcpusText}, ${getMemory(vm)} Memory`}; + + const memoryBase = convertToBaseValue(getMemory(vm)); + const memoryText = humanizeDecimalBytes(memoryBase).string; + + return <>{`${flavor || ''}${flavor ? ': ' : ''}${vcpusText}, ${memoryText} Memory`}; }; type FlavorTextProps = { diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx index 5c560822821..91853389a26 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/modals/vm-flavor-modal/vm-flavor-modal.tsx @@ -68,10 +68,7 @@ const VMFlavorModal = withHandlePromise((props: VMFlavornModalProps) => { const sourceCPURaw = isVM(vmLike) ? getCPU(vmLike as VMKind) : getCPU(selectVM(vmLike as TemplateKind)); - let sourceCPU = parseInt(sourceCPURaw, 10); - if (!sourceCPU) { - sourceCPU = vCPUCount(sourceCPURaw); - } + const sourceCPU = vCPUCount(sourceCPURaw); const [flavor, setFlavor] = React.useState(vmFlavor); const [mem, setMem] = React.useState( @@ -97,7 +94,6 @@ const VMFlavorModal = withHandlePromise((props: VMFlavornModalProps) => { 'kubevirt-vm-flavor-modal__content-generic': flavor !== CUSTOM_FLAVOR, }); - console.log('--- flavors: ', flavors); return (
Edit Flavor diff --git a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-patches.ts b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-patches.ts index 04079b9e188..7e7c9b60698 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-patches.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-patches.ts @@ -2,7 +2,7 @@ import * as _ from 'lodash'; import { Patch, TemplateKind } from '@console/internal/module/k8s'; import { VMLikeEntityKind, VMKind, CPU } from '../../../types'; import { getAnnotations, getDescription } from '../../../selectors/selectors'; -import { getFlavor, getCPU, getMemory, isVM } from '../../../selectors/vm'; +import { getFlavor, getCPU, getMemory, isVM, parseCPU, DEFAULT_CPU } from '../../../selectors/vm'; import { CUSTOM_FLAVOR, TEMPLATE_FLAVOR_LABEL } from '../../../constants'; import { selectVM, getTemplateForFlavor } from '../../../selectors/vm-template/selectors'; import { getVMLikePatches } from '../vm-template'; @@ -133,7 +133,7 @@ const getMemoryPatch = (vm: VMKind, memory: string): Patch => { const getUpdateCpuMemoryPatch = (vm: VMKind, cpu: CPU, memory: string): Patch[] => { const patch = []; - const vmCpu = getCPU(vm); + const vmCpu = parseCPU(getCPU(vm)); const vmMemory = getMemory(vm); if (memory !== vmMemory || !isCPUEqual(cpu, vmCpu)) { @@ -219,7 +219,7 @@ export const getUpdateFlavorPatches = ( let customMem = mem; if (flavor !== CUSTOM_FLAVOR) { const templateVm = selectVM(template); - customCpu = getCPU(templateVm) as CPU; + customCpu = parseCPU(getCPU(templateVm), DEFAULT_CPU); customMem = getMemory(templateVm); } diff --git a/frontend/packages/kubevirt-plugin/src/selectors/vm/cpu.ts b/frontend/packages/kubevirt-plugin/src/selectors/vm/cpu.ts index 4347135c0be..0e9cd62fba1 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/vm/cpu.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/vm/cpu.ts @@ -1,13 +1,24 @@ -export const vCPUCount = (sourceCPURaw: CPURaw) => { - return ( - (parseInt(sourceCPURaw.sockets, 10) || 1) * - (parseInt(sourceCPURaw.cores, 10) || 1) * - (parseInt(sourceCPURaw.threads, 10) || 1) - ); +import { CPURaw, CPU } from '../../types'; + +export const DEFAULT_CPU: CPU = { sockets: 1, cores: 1, threads: 1 }; + +export const parseCPU = (sourceCPURaw: CPURaw, defaultValue?: CPU): CPU => { + if (!sourceCPURaw) { + return defaultValue; + } + + if (typeof sourceCPURaw === 'string') { + return { sockets: 1, cores: parseInt(sourceCPURaw as string, 10), threads: 1 }; + } + + return { + sockets: parseInt(sourceCPURaw.sockets, 10) || 1, + cores: parseInt(sourceCPURaw.cores, 10) || 1, + threads: parseInt(sourceCPURaw.threads, 10) || 1, + }; }; -type CPURaw = { - sockets: string; - cores: string; - threads: string; +export const vCPUCount = (sourceCPURaw: CPURaw): number => { + const cpu = parseCPU(sourceCPURaw, DEFAULT_CPU); + return cpu.sockets * cpu.cores * cpu.threads; }; diff --git a/frontend/packages/kubevirt-plugin/src/selectors/vm/selectors.ts b/frontend/packages/kubevirt-plugin/src/selectors/vm/selectors.ts index e9482a2be40..8def016c841 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/vm/selectors.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/vm/selectors.ts @@ -8,7 +8,7 @@ import { TEMPLATE_OS_NAME_ANNOTATION, TEMPLATE_WORKLOAD_LABEL, } from '../../constants/vm'; -import { VMKind, VMLikeEntityKind } from '../../types'; +import { VMKind, VMLikeEntityKind, CPURaw } from '../../types'; import { findKeySuffixValue, getValueByPrefix } from '../utils'; import { getAnnotations, getLabels } from '../selectors'; import { getDiskBus } from './disk'; @@ -17,7 +17,7 @@ import { Network } from './types'; export const getMemory = (vm: VMKind) => _.get(vm, 'spec.template.spec.domain.resources.requests.memory'); -export const getCPU = (vm: VMKind) => _.get(vm, 'spec.template.spec.domain.cpu'); +export const getCPU = (vm: VMKind): CPURaw => _.get(vm, 'spec.template.spec.domain.cpu'); export const getDisks = (vm: VMKind) => _.get(vm, 'spec.template.spec.domain.devices.disks', []); export const getInterfaces = (vm: VMKind) => _.get(vm, 'spec.template.spec.domain.devices.interfaces', []); diff --git a/frontend/packages/kubevirt-plugin/src/types/vm/index.ts b/frontend/packages/kubevirt-plugin/src/types/vm/index.ts index 983d953951e..980e5bd770b 100644 --- a/frontend/packages/kubevirt-plugin/src/types/vm/index.ts +++ b/frontend/packages/kubevirt-plugin/src/types/vm/index.ts @@ -64,3 +64,11 @@ export type CPU = { cores: number; threads: number; }; + +export type CPURaw = + | { + sockets: string; + cores: string; + threads: string; + } + | string; From f0e35fee78911a81bef524f4203a45ab923ec378 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Fri, 6 Sep 2019 09:24:33 +0200 Subject: [PATCH 16/16] Move edit button to value in the VM Detail --- .../src/components/edit-button.tsx | 10 ++++--- .../vm-templates/vm-template-resource.tsx | 30 +++++++------------ .../src/components/vms/vm-resource.tsx | 24 +++++---------- 3 files changed, 25 insertions(+), 39 deletions(-) diff --git a/frontend/packages/kubevirt-plugin/src/components/edit-button.tsx b/frontend/packages/kubevirt-plugin/src/components/edit-button.tsx index 48d9c72b3b4..6c2d5b19ea4 100644 --- a/frontend/packages/kubevirt-plugin/src/components/edit-button.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/edit-button.tsx @@ -1,16 +1,17 @@ import * as React from 'react'; -import { Button } from '@patternfly/react-core'; export const EditButton: React.FC = (props) => { - const { canEdit, onClick } = props; + const { canEdit, onClick, children } = props; if (canEdit) { return ( - ); } @@ -18,6 +19,7 @@ export const EditButton: React.FC = (props) => { }; type EditButtonProps = { + children: any; canEdit: boolean; onClick: React.MouseEventHandler; }; diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-templates/vm-template-resource.tsx b/frontend/packages/kubevirt-plugin/src/components/vm-templates/vm-template-resource.tsx index 03779fadb47..057f435c818 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vm-templates/vm-template-resource.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vm-templates/vm-template-resource.tsx @@ -32,18 +32,14 @@ export const VMTemplateResourceSummary: React.FC return ( -
- Description - {canUpdateTemplate && ( -
+
Description
- {description} + vmDescriptionModal({ vmLikeEntity: template })} + > + {description} +
Operating System
@@ -77,15 +73,11 @@ export const VMTemplateDetailsList: React.FC = ({ DASH )}
-
- Flavor - vmFlavorModal({ vmLike: template })} - /> -
+
Flavor
- + vmFlavorModal({ vmLike: template })}> + +
Provision Source
diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/vm-resource.tsx b/frontend/packages/kubevirt-plugin/src/components/vms/vm-resource.tsx index 18569fcce0f..cd39b4ce419 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vms/vm-resource.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vms/vm-resource.tsx @@ -34,18 +34,11 @@ export const VMResourceSummary: React.FC = ({ vm, canUpd return ( -
- Description - {canUpdateVM && ( -
+
Description
- {description} + vmDescriptionModal({ vmLikeEntity: vm })}> + {description} +
Operating System
@@ -106,12 +99,11 @@ export const VMDetailsList: React.FC = ({
Node
{}
-
- Flavor - vmFlavorModal({ vmLike: vm })} /> -
+
Flavor
- + vmFlavorModal({ vmLike: vm })}> + +
Workload Profile
{getWorkloadProfile(vm) || DASH}