diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/stateUpdate/vmSettings/prefill-vm-template-state-update.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/stateUpdate/vmSettings/prefill-vm-template-state-update.ts index 6c273f85b13..8b591ca2df5 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/stateUpdate/vmSettings/prefill-vm-template-state-update.ts +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/redux/stateUpdate/vmSettings/prefill-vm-template-state-update.ts @@ -40,7 +40,6 @@ import { parseCPU, isWinToolsImage, } from '../../../../../selectors/vm'; -import { selectVM } from '../../../../../selectors/vm-template/selectors'; import { getTemplateFlavors, getTemplateOperatingSystems, @@ -62,6 +61,7 @@ import { DataVolumeWrapper } from '../../../../../k8s/wrapper/vm/data-volume-wra import { V1alpha1DataVolume } from '../../../../../types/vm/disk/V1alpha1DataVolume'; import { joinIDs } from '../../../../../utils'; import { VM_TEMPLATE_NAME_PARAMETER } from '../../../../../constants/vm-templates'; +import { selectVM } from '../../../../../selectors/vm-template/basic'; export const prefillVmTemplateUpdater = ({ id, dispatch, getState }: UpdateOptions) => { const state = getState(); diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/selectors/template.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/selectors/template.ts index 5ece3f0e038..04dcb34dfd4 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/selectors/template.ts +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/selectors/template.ts @@ -8,6 +8,8 @@ import { VMSettingsField, VMWizardProps } from '../types'; import { iGetLoadedCommonData } from './immutable/selectors'; import { iGetVmSettingAttribute, iGetVmSettingValue } from './immutable/vm-settings'; +const { warn } = console; + const getValidationsFromTemplates = (templates): TemplateValidations[] => templates.map( (relevantTemplate) => new TemplateValidations(iGetTemplateValidations(relevantTemplate)), @@ -52,7 +54,7 @@ export const getTemplateValidation = (state, id: string): TemplateValidations => const templateValidations = getTemplateValidations(state, id); if (templateValidations && templateValidations.length > 0) { templateValidations.length > 1 && - console.warn('WARNING: getTemplateValidation: multiple template validations detected!'); + warn('WARNING: getTemplateValidation: multiple template validations detected!'); return templateValidations[0]; } diff --git a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/types.ts b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/types.ts index e4018d04c3c..d79f1d11d58 100644 --- a/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/types.ts +++ b/frontend/packages/kubevirt-plugin/src/components/create-vm-wizard/types.ts @@ -4,7 +4,7 @@ import { getStringEnumValues } from '../../utils/types'; import { V1Network, V1NetworkInterface, VMKind } from '../../types/vm'; import { NetworkInterfaceWrapper } from '../../k8s/wrapper/vm/network-interface-wrapper'; import { NetworkWrapper } from '../../k8s/wrapper/vm/network-wrapper'; -import { UIDiskValidation, UINetworkInterfaceValidation } from '../../utils/validations/vm'; +import { UINetworkInterfaceValidation } from '../../utils/validations/vm/nic'; import { V1Disk } from '../../types/vm/disk/V1Disk'; import { V1Volume } from '../../types/vm/disk/V1Volume'; import { V1alpha1DataVolume } from '../../types/vm/disk/V1alpha1DataVolume'; @@ -14,6 +14,7 @@ import { DataVolumeWrapper } from '../../k8s/wrapper/vm/data-volume-wrapper'; import { IDReferences } from '../../utils/redux/id-reference'; import { V1PersistentVolumeClaim } from '../../types/vm/disk/V1PersistentVolumeClaim'; import { PersistentVolumeClaimWrapper } from '../../k8s/wrapper/vm/persistent-volume-claim-wrapper'; +import { UIDiskValidation } from '../../utils/validations/vm/types'; export enum VMWizardTab { // order important VM_SETTINGS = 'VM_SETTINGS', diff --git a/frontend/packages/kubevirt-plugin/src/components/dashboards-page/vm-dashboard/vm-activity.tsx b/frontend/packages/kubevirt-plugin/src/components/dashboards-page/vm-dashboard/vm-activity.tsx index e21f6d49dfa..1b2edad4ec6 100644 --- a/frontend/packages/kubevirt-plugin/src/components/dashboards-page/vm-dashboard/vm-activity.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/dashboards-page/vm-dashboard/vm-activity.tsx @@ -17,12 +17,12 @@ import { withDashboardResources, DashboardItemProps, } from '@console/internal/components/dashboard/with-dashboard-resources'; -import { VMKind } from '../../../types'; import { VirtualMachineModel } from '../../../models'; import { getVmEventsFilters } from '../../../selectors/event'; import { VMDashboardContext } from '../../vms/vm-dashboard-context'; +import { VMILikeEntityKind } from '../../../types/vmLike'; -const combinedVmFilter = (vm: VMKind): EventFilterFuncion => (event) => +const combinedVmFilter = (vm: VMILikeEntityKind): EventFilterFuncion => (event) => getVmEventsFilters(vm).some((filter) => filter(event.involvedObject)); const getEventsResource = (namespace: string): FirehoseResource => ({ @@ -56,13 +56,14 @@ const RecentEvent = withDashboardResources( ); export const VMActivityCard: React.FC = () => { - const { vm } = React.useContext(VMDashboardContext); + const { vm, vmi } = React.useContext(VMDashboardContext); + const vmiLike = vm || vmi; const [paused, setPaused] = React.useState(false); const togglePause = React.useCallback(() => setPaused(!paused), [paused]); - const name = getName(vm); - const namespace = getNamespace(vm); + const name = getName(vmiLike); + const namespace = getNamespace(vmiLike); const viewEventsLink = `${resourcePath(VirtualMachineModel.kind, name, namespace)}/events`; return ( @@ -76,7 +77,7 @@ export const VMActivityCard: React.FC = () => { - + @@ -86,7 +87,7 @@ export const VMActivityCard: React.FC = () => { type EventFilterFuncion = (event: EventKind) => boolean; type RecentEventProps = DashboardItemProps & { - vm: VMKind; + vm: VMILikeEntityKind; paused: boolean; setPaused: (paused: boolean) => void; }; diff --git a/frontend/packages/kubevirt-plugin/src/components/dashboards-page/vm-dashboard/vm-alerts.tsx b/frontend/packages/kubevirt-plugin/src/components/dashboards-page/vm-dashboard/vm-alerts.tsx index 025d6f09252..a39a14aa687 100644 --- a/frontend/packages/kubevirt-plugin/src/components/dashboards-page/vm-dashboard/vm-alerts.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/dashboards-page/vm-dashboard/vm-alerts.tsx @@ -15,7 +15,7 @@ const isGuestAgentInstalled = (vmi: VMIKind) => { }; export const VMAlerts: React.FC = ({ vm, vmi }) => ( - + {vmi && vmi.status && !isGuestAgentInstalled(vmi) && ( = () => { const vmDashboardContext = React.useContext(VMDashboardContext); - const { vm, vmi, pods, migrations } = vmDashboardContext; + const { vm, vmi, pods } = vmDashboardContext; + const vmiLike = vm || vmi; - const vmStatus = getVMStatus({ vm, vmi, pods, migrations }); - const { launcherPod } = vmStatus; + const launcherPod = findVMPod(vmiLike, pods); - const ipAddrs = getVmiIpAddressesString(vmi, vmStatus); + const ipAddrs = getVmiIpAddresses(vmi).join(', '); - const isNodeLoading = !vm || !pods || !vmStatus; - const name = getName(vm); - const namespace = getNamespace(vm); + const isNodeLoading = !vmiLike || !pods; + const name = getName(vmiLike); + const namespace = getNamespace(vmiLike); const viewAllLink = `${resourcePath( VirtualMachineModel.kind, @@ -48,26 +47,32 @@ export const VMDetailsCard: React.FC = () => { - + {name} - - + + - - + + - + {launcherPod && } {ipAddrs} diff --git a/frontend/packages/kubevirt-plugin/src/components/dashboards-page/vm-dashboard/vm-inventory-card.tsx b/frontend/packages/kubevirt-plugin/src/components/dashboards-page/vm-dashboard/vm-inventory-card.tsx index 2f3b93a0b5d..66bb6da5d35 100644 --- a/frontend/packages/kubevirt-plugin/src/components/dashboards-page/vm-dashboard/vm-inventory-card.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/dashboards-page/vm-dashboard/vm-inventory-card.tsx @@ -9,25 +9,26 @@ import { getName, getNamespace } from '@console/shared'; import InventoryItem from '@console/shared/src/components/dashboard/inventory-card/InventoryItem'; import { resourcePath } from '@console/internal/components/utils'; import { VMDashboardContext } from '../../vms/vm-dashboard-context'; +import { getVMLikeModel } from '../../../selectors/vm/vmlike'; import { getNetworks, getDisks } from '../../../selectors/vm'; import { getVMINetworks, getVMIDisks } from '../../../selectors/vmi'; -import { VirtualMachineModel } from '../../../models'; import { VM_DETAIL_DISKS_HREF, VM_DETAIL_NETWORKS_HREF } from '../../../constants'; export const VMInventoryCard: React.FC = () => { const vmDashboardContext = React.useContext(VMDashboardContext); const { vm, vmi } = vmDashboardContext; + const vmiLike = vm || vmi; - const isLoading = !vm; - const name = getName(vm); - const namespace = getNamespace(vm); + const isLoading = !vmiLike; + const name = getName(vmiLike); + const namespace = getNamespace(vmiLike); // prefer vmi over vm if available (means: is running) - const nicCount = vmi && vmi.spec ? getVMINetworks(vmi).length : getNetworks(vm).length; - const diskCount = vmi && vmi.spec ? getVMIDisks(vmi).length : getDisks(vm).length; + const nicCount = vm ? getNetworks(vm).length : getVMINetworks(vmi).length; + const diskCount = vm ? getDisks(vm).length : getVMIDisks(vmi).length; // TODO: per design, snapshots should be added here (snapshots are not implemented at all atm) - const basePath = resourcePath(VirtualMachineModel.kind, name, namespace); + const basePath = resourcePath(getVMLikeModel(vmiLike).kind, name, namespace); const DisksTitle = React.useCallback( ({ children }) => {children}, [basePath], diff --git a/frontend/packages/kubevirt-plugin/src/components/dashboards-page/vm-dashboard/vm-utilization.tsx b/frontend/packages/kubevirt-plugin/src/components/dashboards-page/vm-dashboard/vm-utilization.tsx index a2c3fcc17f9..cc8551796ae 100644 --- a/frontend/packages/kubevirt-plugin/src/components/dashboards-page/vm-dashboard/vm-utilization.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/dashboards-page/vm-dashboard/vm-utilization.tsx @@ -34,10 +34,11 @@ const humanizeCpuCores = (v) => { export const VMUtilizationCard: React.FC = () => { const [timestamps, setTimestamps] = React.useState(); const [duration, setDuration] = useMetricDuration(); - const { vm, pods } = React.useContext(VMDashboardContext); - const vmName = getName(vm); - const namespace = getNamespace(vm); - const launcherPodName = getName(findVMPod(vm, pods)); + const { vm, vmi, pods } = React.useContext(VMDashboardContext); + const vmiLike = vm || vmi; + const vmName = getName(vmiLike); + const namespace = getNamespace(vmiLike); + const launcherPodName = getName(findVMPod(vmiLike, pods)); const queries = React.useMemo( () => getUtilizationQueries({ diff --git a/frontend/packages/kubevirt-plugin/src/components/flavor-text.tsx b/frontend/packages/kubevirt-plugin/src/components/flavor-text.tsx index d1c0a81403a..d29db1c47c0 100644 --- a/frontend/packages/kubevirt-plugin/src/components/flavor-text.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/flavor-text.tsx @@ -1,18 +1,22 @@ import * as _ from 'lodash'; import { convertToBaseValue, humanizeBinaryBytes } from '@console/internal/components/utils'; -import { getFlavor, vCPUCount, getCPU, getMemory, asVM } from '../selectors/vm'; -import { VMLikeEntityKind } from '../types'; +import { vCPUCount } from '../selectors/vm'; +import { CPURaw } from '../types/vm'; -export const getFlavorText = (vmLike: VMLikeEntityKind) => { - const vm = asVM(vmLike); - - const flavor = _.capitalize(getFlavor(vmLike)); - - const vcpusCount = vCPUCount(getCPU(vm)); +export const getFlavorText = ({ + cpu, + memory, + flavor, +}: { + cpu: CPURaw; + memory: string; + flavor: string; +}) => { + const vcpusCount = vCPUCount(cpu); const vcpusText = `${vcpusCount} vCPU${vcpusCount > 1 ? 's' : ''}`; - const memoryBase = convertToBaseValue(getMemory(vm)); + const memoryBase = convertToBaseValue(memory); const memoryText = humanizeBinaryBytes(memoryBase).string; - return `${flavor || ''}${flavor ? ': ' : ''}${vcpusText}, ${memoryText} Memory`; + return `${_.capitalize(flavor) || ''}${flavor ? ': ' : ''}${vcpusText}, ${memoryText} Memory`; }; diff --git a/frontend/packages/kubevirt-plugin/src/components/ip-addresses.ts b/frontend/packages/kubevirt-plugin/src/components/ip-addresses.ts deleted file mode 100644 index 9df1fea810d..00000000000 --- a/frontend/packages/kubevirt-plugin/src/components/ip-addresses.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { VMIKind } from '../types'; -import { VMStatus, isVmOff } from '../statuses/vm/vm'; -import { getVmiIpAddresses } from '../selectors/vmi/ip-address'; - -// the vmStatus is precomputed by caller for optimization -export const getVmiIpAddressesString = (vmi: VMIKind, vmStatus: VMStatus): string => { - const vmIsOff = vmStatus && isVmOff(vmStatus); - const ipAddresses = vmIsOff ? [] : getVmiIpAddresses(vmi); - return ipAddresses.length > 0 ? ipAddresses.join(', ') : null; -}; diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/boot-order-modal/boot-order-modal.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/boot-order-modal/boot-order-modal.tsx index e0aafae7732..1bb55ccfb22 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/boot-order-modal/boot-order-modal.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/modals/boot-order-modal/boot-order-modal.tsx @@ -6,7 +6,8 @@ import { withHandlePromise, HandlePromiseProps } from '@console/internal/compone import { ModalComponentProps } from '@console/internal/components/factory'; import { k8sPatch } from '@console/internal/module/k8s'; import { PatchBuilder, PatchOperation } from '@console/shared/src/k8s'; -import { VMLikeEntityKind, BootableDeviceType } from '../../../types'; +import { BootableDeviceType } from '../../../types'; +import { VMLikeEntityKind } from '../../../types/vmLike'; import { getVMLikeModel, getDevices } from '../../../selectors/vm'; import { getVMLikePatches } from '../../../k8s/patches/vm-template'; import { BootOrder, deviceKey } from '../../boot-order'; diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/cdrom-modal.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/cdrom-modal.tsx index ebdfe782c58..2eea161c563 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/cdrom-modal.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/cdrom-modal.tsx @@ -22,11 +22,13 @@ import { isVMRunning, } from '../../../selectors/vm/selectors'; import { isValidationError, validateURL } from '../../../utils/validations/common'; -import { VMKind, VMLikeEntityKind } from '../../../types'; +import { VMLikeEntityKind } from '../../../types/vmLike'; import { CDRomRow } from './cdrom-row'; import { getAvailableCDName } from './helpers'; -import { initialDisk, WINTOOLS_CONTAINER_NAMES, StorageType, CD, CDMap } from './constants'; +import { initialDisk, WINTOOLS_CONTAINER_NAMES, StorageType } from './constants'; import './cdrom-modal.scss'; +import { CD, CDMap } from './types'; +import { VMKind } from '../../../types/vm'; export const CDRomModal = withHandlePromise((props: CDRomModalProps) => { const { @@ -54,13 +56,13 @@ export const CDRomModal = withHandlePromise((props: CDRomModalProps) => { }; const container = getContainerImageByDisk(vm, name); if (container) { - if (_.includes(WINTOOLS_CONTAINER_NAMES, container)) + if (_.includes(WINTOOLS_CONTAINER_NAMES, container)) { cd = { ...cd, type: StorageType.WINTOOLS, windowsTools: container, }; - else { + } else { cd = { ...cd, type: StorageType.CONTAINER, container }; } } diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/cdrom-row.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/cdrom-row.tsx index 7d567296b3c..26875a17d01 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/cdrom-row.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/cdrom-row.tsx @@ -16,7 +16,8 @@ import { FirehoseResult } from '@console/internal/components/utils'; import { K8sResourceSelectRow } from '../../form/k8s-resource-select-row'; import { VMKind } from '../../../types'; import { FormSelectPlaceholderOption } from '../../form/form-select-placeholder-option'; -import { StorageType, CD, CD_SIZE, CD_STORAGE_CLASS } from './constants'; +import { StorageType, CD_SIZE, CD_STORAGE_CLASS } from './constants'; +import { CD } from './types'; export const CDRomRow: React.FC = ({ cd, diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/constants.ts b/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/constants.ts index 667aae4dcdb..13706eb18b8 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/constants.ts +++ b/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/constants.ts @@ -1,28 +1,3 @@ -import { V1CDRomTarget } from '../../../types/vm/disk/V1CDRomTarget'; - -export type CD = { - name: string; - bootOrder?: number; - cdrom?: V1CDRomTarget; - - // UI - changed?: boolean; - newCD?: boolean; - pvc?: string; - container?: string; - type?: string; - bus?: string; - url?: string; - windowsTools?: string; - storageClass?: string; - size?: string | number; - isURLValid?: boolean; -}; - -export type CDMap = { - [name: string]: CD; -}; - export const CD_SIZE = 'size'; export const CD_STORAGE_CLASS = 'storageClass'; diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/helpers.ts b/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/helpers.ts index c42a4ca7e80..b8cb559f386 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/helpers.ts +++ b/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/helpers.ts @@ -1,4 +1,4 @@ -import { CD } from './constants'; +import { CD } from './types'; export const getAvailableCDName = (cds: CD[]) => { const cdSet = new Set(cds.map((cd) => cd.name)); diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/types.ts b/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/types.ts new file mode 100644 index 00000000000..2f466d0911f --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/types.ts @@ -0,0 +1,24 @@ +import { V1CDRomTarget } from '../../../types/vm/disk/V1CDRomTarget'; + +export type CD = { + name: string; + bootOrder?: number; + cdrom?: V1CDRomTarget; + + // UI + changed?: boolean; + newCD?: boolean; + pvc?: string; + container?: string; + type?: string; + bus?: string; + url?: string; + windowsTools?: string; + storageClass?: string; + size?: string | number; + isURLValid?: boolean; +}; + +export type CDMap = { + [name: string]: CD; +}; diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/index.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/vm-cdrom-modal.tsx similarity index 96% rename from frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/index.tsx rename to frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/vm-cdrom-modal.tsx index 1977f299f19..d7992d2104d 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/index.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/modals/cdrom-vm-modal/vm-cdrom-modal.tsx @@ -3,7 +3,7 @@ import { getNamespace } from '@console/shared'; import { Firehose } from '@console/internal/components/utils'; import { createModalLauncher, ModalComponentProps } from '@console/internal/components/factory'; import { PersistentVolumeClaimModel, StorageClassModel } from '@console/internal/models'; -import { VMLikeEntityKind } from '../../../types'; +import { VMLikeEntityKind } from '../../../types/vmLike'; import { asVM } from '../../../selectors/vm'; import { V1alpha1DataVolume } from '../../../types/vm/disk/V1alpha1DataVolume'; import { CDRomModal } from './cdrom-modal'; diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/dedicated-resources-modal/dedicated-resources-modal.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/dedicated-resources-modal/dedicated-resources-modal.tsx index 818f2c71555..753a78ded7a 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/dedicated-resources-modal/dedicated-resources-modal.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/modals/dedicated-resources-modal/dedicated-resources-modal.tsx @@ -13,7 +13,7 @@ import { 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 { VMLikeEntityKind } from '../../../types/vmLike'; import { getLoadedData, isLoaded, getLoadError } from '../../../utils'; import { RESOURCE_NO_NODES_AVAILABLE, DEDICATED_RESOURCES } from './consts'; import './dedicated-resources-modal.scss'; diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/delete-device-modal/delete-device-modal.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/delete-device-modal/delete-device-modal.tsx index a564a5dfdd9..0156d93e005 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/delete-device-modal/delete-device-modal.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/modals/delete-device-modal/delete-device-modal.tsx @@ -8,7 +8,7 @@ import { } from '@console/internal/components/factory'; import { k8sPatch } from '@console/internal/module/k8s'; import { getName } from '@console/shared'; -import { VMLikeEntityKind } from '../../../types'; +import { VMLikeEntityKind } from '../../../types/vmLike'; import { getVMLikeModel } from '../../../selectors/vm'; import { getRemoveDiskPatches } from '../../../k8s/patches/vm/vm-disk-patches'; import { getRemoveNICPatches } from '../../../k8s/patches/vm/vm-nic-patches'; diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/disk-modal/disk-modal-enhanced.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/disk-modal/disk-modal-enhanced.tsx index bad941460e5..cbd05dd6251 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/disk-modal/disk-modal-enhanced.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/modals/disk-modal/disk-modal-enhanced.tsx @@ -12,7 +12,7 @@ import { } from '@console/internal/models'; import { getLoadedData } from '../../../utils'; import { asVM, getVMLikeModel } from '../../../selectors/vm'; -import { VMLikeEntityKind } from '../../../types'; +import { VMLikeEntityKind } from '../../../types/vmLike'; import { DiskWrapper } from '../../../k8s/wrapper/vm/disk-wrapper'; import { VolumeWrapper } from '../../../k8s/wrapper/vm/volume-wrapper'; import { DataVolumeWrapper } from '../../../k8s/wrapper/vm/data-volume-wrapper'; diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/nic-modal/nic-modal-enhanced.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/nic-modal/nic-modal-enhanced.tsx index 25ebe3d1fea..7c1be3741f7 100644 --- a/frontend/packages/kubevirt-plugin/src/components/modals/nic-modal/nic-modal-enhanced.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/modals/nic-modal/nic-modal-enhanced.tsx @@ -9,7 +9,7 @@ import { getLoadedData } from '../../../utils'; import { NetworkType } from '../../../constants/vm'; import { getInterfaces, getUsedNetworks, asVM, getVMLikeModel } from '../../../selectors/vm'; import { NetworkInterfaceWrapper } from '../../../k8s/wrapper/vm/network-interface-wrapper'; -import { VMLikeEntityKind } from '../../../types'; +import { VMLikeEntityKind } from '../../../types/vmLike'; import { getUpdateNICPatches } from '../../../k8s/patches/vm/vm-nic-patches'; import { getSimpleName } from '../../../selectors/utils'; import { NetworkWrapper } from '../../../k8s/wrapper/vm/network-wrapper'; 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 6f1f4befdc8..6e93ac7c995 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 @@ -10,7 +10,7 @@ import { } from '@console/internal/components/factory'; import { k8sPatch } from '@console/internal/module/k8s'; import { getDescription } from '../../../selectors/selectors'; -import { VMLikeEntityKind } from '../../../types'; +import { VMGenericLikeEntityKind } from '../../../types/vmLike'; import { getVMLikeModel } from '../../../selectors/vm'; import { getUpdateDescriptionPatches } from '../../../k8s/patches/vm/vm-patches'; @@ -57,7 +57,7 @@ export const VMDescriptionModal = withHandlePromise((props: VMDescriptionModalPr export type VMDescriptionModalProps = HandlePromiseProps & ModalComponentProps & { - vmLikeEntity: VMLikeEntityKind; + vmLikeEntity: VMGenericLikeEntityKind; }; export const vmDescriptionModal = createModalLauncher(VMDescriptionModal); 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 1c3528cc582..734c8cc3810 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 @@ -18,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 { VMLikeEntityKind } from '../../../types'; +import { VMLikeEntityKind } from '../../../types/vmLike'; import { getFlavor, getMemory, diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-disks/disk-row.tsx b/frontend/packages/kubevirt-plugin/src/components/vm-disks/disk-row.tsx index abc4d0b9d8d..7dbe92f9a3a 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vm-disks/disk-row.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vm-disks/disk-row.tsx @@ -9,8 +9,8 @@ import { import { DASH, dimensifyRow, getDeletetionTimestamp } from '@console/shared'; import { TemplateModel } from '@console/internal/models'; import { deleteDeviceModal, DeviceType } from '../modals/delete-device-modal'; -import { VMLikeEntityKind } from '../../types'; -import { asVM, isVM, isVMRunning } from '../../selectors/vm'; +import { VMLikeEntityKind } from '../../types/vmLike'; +import { asVM, isVM, isVMI, isVMRunning } from '../../selectors/vm'; import { VirtualMachineModel } from '../../models'; import { ValidationCell } from '../table/validation-cell'; import { VMNicRowActionOpts } from '../vm-nics/types'; @@ -74,7 +74,7 @@ const getActions = ( opts: VMStorageRowActionOpts, ) => { const actions = []; - if (isVMRunning(asVM(vmLikeEntity))) { + if (isVMI(vmLikeEntity) || isVMRunning(asVM(vmLikeEntity))) { return actions; } @@ -163,7 +163,10 @@ export const DiskRow: React.FC = ({ withProgress, })} isDisabled={ - isDisabled || !!getDeletetionTimestamp(vmLikeEntity) || isVMRunning(asVM(vmLikeEntity)) + isDisabled || + isVMI(vmLikeEntity) || + !!getDeletetionTimestamp(vmLikeEntity) || + isVMRunning(asVM(vmLikeEntity)) } id={`kebab-for-${disk.getName()}`} /> diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-disks/types.ts b/frontend/packages/kubevirt-plugin/src/components/vm-disks/types.ts index 198679e0fee..6571f8872ca 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vm-disks/types.ts +++ b/frontend/packages/kubevirt-plugin/src/components/vm-disks/types.ts @@ -1,5 +1,5 @@ import { ValidationObject } from '@console/shared'; -import { VMLikeEntityKind } from '../../types'; +import { VMLikeEntityKind } from '../../types/vmLike'; import { CombinedDisk } from '../../k8s/wrapper/vm/combined-disk'; export type StorageSimpleData = { diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-disks/vm-disks.tsx b/frontend/packages/kubevirt-plugin/src/components/vm-disks/vm-disks.tsx index 03b558ad0a7..c7a573b5c96 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vm-disks/vm-disks.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vm-disks/vm-disks.tsx @@ -8,7 +8,7 @@ import { K8sResourceKind } from '@console/internal/module/k8s'; import { dimensifyHeader, getNamespace } from '@console/shared'; import { sortable } from '@patternfly/react-table'; import { DataVolumeModel } from '../../models'; -import { VMLikeEntityKind } from '../../types'; +import { VMGenericLikeEntityKind } from '../../types/vmLike'; import { VMLikeEntityTabProps } from '../vms/types'; import { getResource } from '../../utils'; import { wrapWithProgress } from '../../utils/utils'; @@ -19,13 +19,14 @@ import { VHW_TYPES } from '../create-vm-wizard/tabs/virtual-hardware-tab/types'; import { StorageBundle } from './types'; import { DiskRow } from './disk-row'; import { diskTableColumnClasses } from './utils'; +import { isVMI } from '../../selectors/vm'; const getStoragesData = ({ vmLikeEntity, datavolumes, pvcs, }: { - vmLikeEntity: VMLikeEntityKind; + vmLikeEntity: VMGenericLikeEntityKind; pvcs: FirehoseResult; datavolumes: FirehoseResult; }): StorageBundle[] => { @@ -110,7 +111,7 @@ export const VMDisksTable: React.FC = ({ }; type VMDisksProps = { - vmLikeEntity?: VMLikeEntityKind; + vmLikeEntity?: VMGenericLikeEntityKind; pvcs?: FirehoseResult; datavolumes?: FirehoseResult; }; @@ -120,25 +121,27 @@ export const VMDisks: React.FC = ({ vmLikeEntity, pvcs, datavolume const withProgress = wrapWithProgress(setIsLocked); return (
-
-
- + {!isVMI(vmLikeEntity) && ( +
+
+ +
-
+ )}
{ - if (isVMRunning(asVM(vmLikeEntity))) { + if (isVMI(vmLikeEntity) || isVMRunning(asVM(vmLikeEntity))) { return []; } const actions = [menuActionEdit, menuActionDelete]; @@ -135,7 +135,10 @@ export const NicRow: React.FC = ({ diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-nics/types.ts b/frontend/packages/kubevirt-plugin/src/components/vm-nics/types.ts index b0b6e7b6f4b..93a967a985d 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vm-nics/types.ts +++ b/frontend/packages/kubevirt-plugin/src/components/vm-nics/types.ts @@ -1,5 +1,5 @@ import { ValidationObject } from '@console/shared'; -import { VMLikeEntityKind } from '../../types'; +import { VMLikeEntityKind } from '../../types/vmLike'; export type NetworkSimpleData = { name?: string; diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-nics/vm-nics.tsx b/frontend/packages/kubevirt-plugin/src/components/vm-nics/vm-nics.tsx index 89a39ee0a65..dbed4f53fd1 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vm-nics/vm-nics.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vm-nics/vm-nics.tsx @@ -4,8 +4,8 @@ import { sortable } from '@patternfly/react-table'; import { useSafetyFirst } from '@console/internal/components/safety-first'; import { createBasicLookup, dimensifyHeader } from '@console/shared'; import { Button, ButtonVariant } from '@patternfly/react-core'; -import { VMLikeEntityKind } from '../../types'; -import { getInterfaces, getNetworks, asVM } from '../../selectors/vm'; +import { VMGenericLikeEntityKind } from '../../types/vmLike'; +import { isVMI } from '../../selectors/vm'; import { VMLikeEntityTabProps } from '../vms/types'; import { NetworkInterfaceWrapper } from '../../k8s/wrapper/vm/network-interface-wrapper'; import { nicModalEnhanced } from '../modals/nic-modal/nic-modal-enhanced'; @@ -15,12 +15,17 @@ import { wrapWithProgress } from '../../utils/utils'; import { NicRow } from './nic-row'; import { NetworkBundle } from './types'; import { nicTableColumnClasses } from './utils'; +import { asVMILikeWrapper } from '../../k8s/wrapper/utils/convert'; -const getNicsData = (vmLikeEntity: VMLikeEntityKind): NetworkBundle[] => { - const vm = asVM(vmLikeEntity); - const networkLookup = createBasicLookup(getNetworks(vm), getSimpleName); +const getNicsData = (vmLikeEntity: VMGenericLikeEntityKind): NetworkBundle[] => { + const vmiLikeWrapper = asVMILikeWrapper(vmLikeEntity); - return getInterfaces(vm).map((nic) => { + const networks = vmiLikeWrapper?.getNetworks() || []; + const interfaces = vmiLikeWrapper?.getInterfaces() || []; + + const networkLookup = createBasicLookup(networks, getSimpleName); + + return interfaces.map((nic) => { const network = networkLookup[nic.name]; const interfaceWrapper = NetworkInterfaceWrapper.initialize(nic); const networkWrapper = NetworkWrapper.initialize(network); @@ -102,25 +107,27 @@ export const VMNics: React.FC = ({ obj: vmLikeEntity }) => const withProgress = wrapWithProgress(setIsLocked); return (
-
-
- + {!isVMI(vmLikeEntity) && ( +
+
+ +
-
+ )}
= ({ migrations, verbose = false, }) => { + const vmLike = vm || vmi; + const statusDetail = getVMStatus({ vm, vmi, pods, migrations }); const linkToVMEvents = `${resourcePath( - VirtualMachineModel.kind, - getName(vm), - getNamespace(vm), + getVMLikeModel(vmLike).kind, + getName(vmLike), + getNamespace(vmLike), )}/${VM_DETAIL_EVENTS_HREF}`; const linkToPodOverview = `${resourcePath( PodModel.kind, 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 b838584706c..e0e5bd93cd4 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 @@ -5,16 +5,16 @@ import { K8sEntityMap } from '@console/shared/src'; 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 { VMCDRomModal } from '../modals/cdrom-vm-modal/vm-cdrom-modal'; import { DedicatedResourcesModal } from '../modals/dedicated-resources-modal/dedicated-resources-modal'; import { getDescription } from '../../selectors/selectors'; import { getCDRoms, - getOperatingSystemName, - getOperatingSystem, + getFlavor, getWorkloadProfile, isDedicatedCPUPlacement, } from '../../selectors/vm/selectors'; +import { getTemplateOperatingSystems } from '../../selectors/vm-template/advanced'; import { getVMTemplateNamespacedName } from '../../selectors/vm-template/selectors'; import { vmFlavorModal } from '../modals'; import { getFlavorText } from '../flavor-text'; @@ -33,6 +33,7 @@ import { VMTemplateLink } from './vm-template-link'; import { TemplateSource } from './vm-template-source'; import './_vm-template-resource.scss'; +import { VMWrapper } from '../../k8s/wrapper/vm/vm-wrapper'; export const VMTemplateResourceSummary: React.FC = ({ template, @@ -42,7 +43,7 @@ export const VMTemplateResourceSummary: React.FC const templateNamespacedName = getVMTemplateNamespacedName(template); const description = getDescription(template); - const os = getOperatingSystemName(asVM(template)) || getOperatingSystem(asVM(template)); + const os = getTemplateOperatingSystems([template])[0]; const workloadProfile = getWorkloadProfile(template); return ( @@ -51,8 +52,8 @@ export const VMTemplateResourceSummary: React.FC title="Description" idValue={prefixedID(id, 'description')} valueClassName="kubevirt-vm-resource-summary__description" - isNotAvail={!description} > + {!description && Not available} vmDescriptionModal({ vmLikeEntity: template })} @@ -62,7 +63,7 @@ export const VMTemplateResourceSummary: React.FC - {os} + {os ? os.name || os.id : null} = ({ const id = getBasicID(template); const devices = getDevices(template); const cds = getCDRoms(asVM(template)); - const flavorText = getFlavorText(template); - const isCPUPinned = isDedicatedCPUPlacement(asVM(template)); + const vm = asVM(template); + const vmWrapper = VMWrapper.initialize(vm); + const flavorText = getFlavorText({ + flavor: getFlavor(vm), + cpu: vmWrapper.getCPU(), + memory: vmWrapper.getMemory(), + }); + const isCPUPinned = isDedicatedCPUPlacement(vm); return (
diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/desktop-viewer-selector.tsx b/frontend/packages/kubevirt-plugin/src/components/vms/desktop-viewer-selector.tsx index f5b51cf1d43..eacc06731e1 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vms/desktop-viewer-selector.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vms/desktop-viewer-selector.tsx @@ -7,7 +7,7 @@ import { DEFAULT_RDP_PORT, TEMPLATE_VM_NAME_LABEL, NetworkType } from '../../con import { VMKind, VMIKind } from '../../types'; import { getNetworks } from '../../selectors/vm'; import { - getVMIInterfaces, + getVMIAvailableStatusInterfaces, RDPConnectionDetailsManualType, VNCConnectionDetailsManualType, } from '../../selectors/vmi'; @@ -22,7 +22,7 @@ const NO_IP_ADDRESS_TITLE = 'Networks misconfigured'; const getVmRdpNetworks = (vm: VMKind, vmi: VMIKind): Network[] => { const networks = getNetworks(vm).filter((n) => n.multus || n.pod); - return getVMIInterfaces(vmi) + return getVMIAvailableStatusInterfaces(vmi) .filter((i) => networks.some((n) => n.name === i.name)) .map((i) => { let ip = i.ipAddress; diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/menu-actions.tsx b/frontend/packages/kubevirt-plugin/src/components/vms/menu-actions.tsx index e36c95efe56..bbda4e86b7c 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vms/menu-actions.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vms/menu-actions.tsx @@ -13,7 +13,7 @@ import { restartVM, startVM, stopVM, VMActionType } from '../../k8s/requests/vm' import { startVMIMigration } from '../../k8s/requests/vmi'; import { cancelMigration } from '../../k8s/requests/vmim'; import { cloneVMModal } from '../modals/clone-vm-modal'; -import { VMCDRomModal } from '../modals/cdrom-vm-modal'; +import { VMCDRomModal } from '../modals/cdrom-vm-modal/vm-cdrom-modal'; import { getVMStatus } from '../../statuses/vm/vm'; import { isVMIPaused } from '../../selectors/vmi'; import { unpauseVMI, VMIActionType } from '../../k8s/requests/vmi/actions'; @@ -191,7 +191,7 @@ export const vmiMenuActions = [ type ExtraResources = { vmi: VMIKind; pods: PodKind[]; migrations: K8sResourceKind[] }; -export const menuActionsCreator = ( +export const vmMenuActionsCreator = ( kindObj: K8sKind, vm: VMKind, { vmi, pods, migrations }: ExtraResources, @@ -203,3 +203,9 @@ export const menuActionsCreator = ( return action(kindObj, vm, { vmi, vmStatus, migration }); }); }; + +export const vmiMenuActionsCreator = (kindObj: K8sKind, vmi: VMIKind) => { + return vmiMenuActions.map((action) => { + return action(kindObj, vmi); + }); +}; diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/types.ts b/frontend/packages/kubevirt-plugin/src/components/vms/types.ts index b1210538456..4dad5248e1a 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vms/types.ts +++ b/frontend/packages/kubevirt-plugin/src/components/vms/types.ts @@ -1,15 +1,19 @@ -import { K8sResourceKind, PodKind, TemplateKind } from '@console/internal/module/k8s'; +import { K8sKind, K8sResourceKind, PodKind, TemplateKind } from '@console/internal/module/k8s'; import { VMIKind, VMKind } from '../../types/vm'; -import { VMLikeEntityKind } from '../../types'; +import { VMGenericLikeEntityKind, VMILikeEntityKind } from '../../types/vmLike'; export type VMTabProps = { - obj?: VMKind; + obj?: VMILikeEntityKind; + vm?: VMKind; vmi?: VMIKind; pods?: PodKind[]; migrations?: K8sResourceKind[]; templates?: TemplateKind[]; + customData: { + kindObj: K8sKind; + }; }; export type VMLikeEntityTabProps = { - obj?: VMLikeEntityKind; + obj?: VMGenericLikeEntityKind; }; diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/vm-console.tsx b/frontend/packages/kubevirt-plugin/src/components/vms/vm-console.tsx index 040128cccd2..b143249c25c 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vms/vm-console.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vms/vm-console.tsx @@ -2,14 +2,10 @@ import * as React from 'react'; import { AccessConsoles, VncConsole } from '@patternfly/react-console'; import { Button } from '@patternfly/react-core'; import { Firehose, FirehoseResult, LoadingInline } from '@console/internal/components/utils'; -import { getName, getNamespace } from '@console/shared'; -import { K8sResourceKind } from '@console/internal/module/k8s'; -import { PodModel, ServiceModel } from '@console/internal/models'; -import { - VirtualMachineInstanceMigrationModel, - VirtualMachineInstanceModel, - VirtualMachineModel, -} from '../../models'; +import { getNamespace } from '@console/shared'; +import { K8sResourceKind, PodKind } from '@console/internal/module/k8s'; +import { ServiceModel } from '@console/internal/models'; +import { VirtualMachineInstanceModel, VirtualMachineModel } from '../../models'; import { findRDPService, getRdpConnectionDetails, @@ -21,10 +17,10 @@ import { RDPConnectionDetailsType, VNCConnectionDetailsType, } from '../../selectors/vmi'; -import { findVMPod } from '../../selectors/pod/selectors'; import { getVMStatus } from '../../statuses/vm/vm'; import { getLoadedData, getResource } from '../../utils'; -import { isVMStarting, isWindows } from '../../selectors/vm'; +import { findVMPod } from '../../selectors/pod/selectors'; +import { isVMStarting, isWindows, asVM, isVM, isVMI } from '../../selectors/vm'; import { VMIKind, VMKind } from '../../types/vm'; import { menuActionStart } from './menu-actions'; import { SerialConsoleConnector } from './serial-console-connector'; @@ -100,11 +96,9 @@ const VMConsoles: React.FC = ({ }; const VmConsolesWrapper: React.FC = (props) => { - const { vm } = props; - const vmi = getLoadedData(props.vmi); - const pods = getLoadedData(props.pods); + const { vm: vmProp, vmi, pods, migrations } = props; + const vm = asVM(vmProp); const services = getLoadedData(props.services); - const migrations = getLoadedData(props.migrations); const onStartVm = () => { const vmStatus = getVMStatus({ vm, vmi, pods, migrations }); @@ -131,35 +125,29 @@ const VmConsolesWrapper: React.FC = (props) => { ); }; -export const VMConsoleFirehose: React.FC = ({ obj: vm }) => { - const name = getName(vm); - const namespace = getNamespace(vm); +export const VMConsoleFirehose: React.FC = ({ + obj: objProp, + vm: vmProp, + vmi: vmiProp, + pods, + migrations, + customData: { kindObj }, +}) => { + const vm = kindObj === VirtualMachineModel && isVM(objProp) ? objProp : vmProp; + const vmi = kindObj === VirtualMachineInstanceModel && isVMI(objProp) ? objProp : vmiProp; - const vmiRes = getResource(VirtualMachineInstanceModel, { - name, - namespace, - isList: false, - prop: 'vmi', - optional: true, - }); + const namespace = getNamespace(vm); const resources = [ - vmiRes, // We probably can not simply match on labels but on Service's spec.selector.[kubevirt/vm] to achieve robust pairing VM-Service. // So read all services and filter on frontend. getResource(ServiceModel, { namespace, prop: 'services' }), - getResource(PodModel, { - namespace, - matchExpressions: [{ key: 'kubevirt.io', operator: 'Exists' }], - prop: 'pods', - }), - getResource(VirtualMachineInstanceMigrationModel, { namespace, prop: 'migrations' }), ]; return (
- +
); @@ -167,10 +155,10 @@ export const VMConsoleFirehose: React.FC = ({ obj: vm }) => { type VmConsolesWrapperProps = { vm?: VMKind; - vmi?: FirehoseResult; - services?: FirehoseResult; - pods?: FirehoseResult; - migrations?: FirehoseResult; + vmi?: VMIKind; + services?: FirehoseResult; + pods?: PodKind[]; + migrations?: K8sResourceKind[]; }; type VMConsolesProps = { diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/vm-create-yaml.tsx b/frontend/packages/kubevirt-plugin/src/components/vms/vm-create-yaml.tsx new file mode 100644 index 00000000000..67fd391c120 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/vms/vm-create-yaml.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; +import { CreateYAML } from '@console/internal/components/create-yaml'; +import { VirtualMachineModel } from '../../models'; + +export const VMCreateYAML = (props: any) => ( + +); diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/vm-dashboard.tsx b/frontend/packages/kubevirt-plugin/src/components/vms/vm-dashboard.tsx index df7c68201e1..00ed436bb4a 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vms/vm-dashboard.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vms/vm-dashboard.tsx @@ -11,13 +11,17 @@ import { VMUtilizationCard, } from '../dashboards-page/vm-dashboard'; import { VMDashboardContext } from './vm-dashboard-context'; +import { asVM, isVMI, isVM } from '../../selectors/vm/vmlike'; const mainCards = [{ Card: VMStatusCard }, { Card: VMUtilizationCard }]; const leftCards = [{ Card: VMDetailsCard }, { Card: VMInventoryCard }]; const rightCards = [{ Card: VMActivityCard }]; export const VMDashboard: React.FC = (props) => { - const { obj: vm, vmi, pods, migrations } = props; + const { obj: objProp, vm: vmProp, vmi: vmiProp, pods, migrations } = props; + + const vm = asVM(objProp) || (isVM(vmProp) && vmProp); + const vmi = (isVMI(objProp) && objProp) || vmiProp; const context = { vm, @@ -37,6 +41,7 @@ export const VMDashboard: React.FC = (props) => { type VMDashboardProps = { obj?: VMKind; + vm?: VMKind; vmi?: VMIKind; pods?: PodKind[]; migrations?: K8sResourceKind[]; 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 ae680519458..e534cf6989a 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 @@ -1,11 +1,14 @@ import * as React from 'react'; 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 { VMDisksFirehose } from '../vm-disks'; import { VMNics } from '../vm-nics'; -import { VirtualMachineInstanceMigrationModel, VirtualMachineInstanceModel } from '../../models'; +import { + VirtualMachineInstanceMigrationModel, + VirtualMachineInstanceModel, + VirtualMachineModel, +} from '../../models'; import { getResource } from '../../utils'; import { VM_DETAIL_OVERVIEW_HREF, @@ -16,22 +19,18 @@ import { import { VMEvents } from './vm-events'; import { VMConsoleFirehose } from './vm-console'; import { VMDetailsFirehose } from './vm-details'; -import { menuActionsCreator } from './menu-actions'; +import { vmMenuActionsCreator } from './menu-actions'; import { VMDashboard } from './vm-dashboard'; export const VirtualMachinesDetailsPage: React.FC = (props) => { - const { name, namespace } = props; + const { name, ns: namespace } = props.match.params; - const resources = [ - getResource(VirtualMachineInstanceModel, { - name, - namespace, - isList: false, - prop: 'vmi', - optional: true, - }), - getResource(PodModel, { namespace, prop: 'pods' }), - getResource(VirtualMachineInstanceMigrationModel, { namespace, prop: 'migrations' }), + const breadcrumbsForVMPage = (match: any) => () => [ + { + name: VirtualMachineModel.labelPlural, + path: `/k8s/ns/${match.params.ns || 'default'}/virtualmachines`, + }, + { name: `${match.params.name} Details`, path: `${match.url}` }, ]; const dashboardPage = { @@ -74,14 +73,34 @@ export const VirtualMachinesDetailsPage: React.FC + ); }; -type VirtualMachinesDetailsPageProps = { - name: string; - namespace: string; - kind: K8sResourceKindReference; +export type VirtualMachinesDetailsPageProps = { match: any; }; diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/vm-details.tsx b/frontend/packages/kubevirt-plugin/src/components/vms/vm-details.tsx index 96c60e34edb..5e007de8404 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vms/vm-details.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vms/vm-details.tsx @@ -9,53 +9,69 @@ import { asAccessReview, } from '@console/internal/components/utils'; import { getNamespace } from '@console/shared'; -import { K8sResourceKind, PodKind, TemplateKind } from '@console/internal/module/k8s'; +import { K8sKind, K8sResourceKind, PodKind, TemplateKind } from '@console/internal/module/k8s'; import { ServiceModel } from '@console/internal/models'; import { ServicesList } from '@console/internal/components/service'; import { VMKind, VMIKind } from '../../types'; import { getLoadedData, getResource } from '../../utils'; -import { VirtualMachineInstanceModel } from '../../models'; +import { VirtualMachineInstanceModel, VirtualMachineModel } from '../../models'; import { getServicesForVmi } from '../../selectors/service'; import { VMResourceSummary, VMDetailsList } from './vm-resource'; import { VMTabProps } from './types'; +import { isVM, isVMI } from '../../selectors/vm/vmlike'; export const VMDetailsFirehose: React.FC = ({ - obj: vm, - vmi, + obj: objProp, + vm: vmProp, + vmi: vmiProp, pods, migrations, templates, + customData: { kindObj }, }) => { - const resources = [getResource(ServiceModel, { namespace: getNamespace(vm), prop: 'services' })]; + const vm = kindObj === VirtualMachineModel && isVM(objProp) ? objProp : vmProp; + const vmi = kindObj === VirtualMachineInstanceModel && isVMI(objProp) ? objProp : vmiProp; + + const resources = [ + getResource(ServiceModel, { namespace: getNamespace(objProp), prop: 'services' }), + ]; return (
- +
); }; const VMDetails: React.FC = (props) => { - const { vm, vmi, pods, migrations, templates, ...restProps } = props; + const { kindObj, vm, vmi, pods, migrations, templates, ...restProps } = props; const mainResources = { vm, vmi, pods, migrations, templates, + kindObj, }; + const vmiLike = kindObj === VirtualMachineModel ? vm : vmi; const vmServicesData = getServicesForVmi(getLoadedData(props.services, []), vmi); - - const canUpdate = useAccessReview(asAccessReview(VirtualMachineInstanceModel, vm, 'patch')); + const canUpdate = useAccessReview(asAccessReview(kindObj, vmiLike || {}, 'patch')) && !!vmiLike; return ( - +
- +
@@ -74,7 +90,8 @@ const VMDetails: React.FC = (props) => { }; type VMDetailsProps = { - vm: VMKind; + kindObj: K8sKind; + vm?: VMKind; pods?: PodKind[]; migrations?: K8sResourceKind[]; vmi?: VMIKind; 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 1d2d5d25631..582992e6f0c 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vms/vm-resource.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vms/vm-resource.tsx @@ -1,41 +1,37 @@ import * as React from 'react'; import { ResourceSummary, NodeLink, ResourceLink } from '@console/internal/components/utils'; -import { PodKind } from '@console/internal/module/k8s'; +import { K8sKind, PodKind } from '@console/internal/module/k8s'; import { getName, getNamespace, getNodeName } 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, vmFlavorModal } from '../modals'; -import { VMCDRomModal } from '../modals/cdrom-vm-modal'; +import { VMCDRomModal } from '../modals/cdrom-vm-modal/vm-cdrom-modal'; import { DedicatedResourcesModal } from '../modals/dedicated-resources-modal/dedicated-resources-modal'; import { BootOrderModal } from '../modals/boot-order-modal/boot-order-modal'; import VMStatusModal from '../modals/vm-status-modal/vm-status-modal'; import { getDescription } from '../../selectors/selectors'; -import { getCDRoms, isDedicatedCPUPlacement } from '../../selectors/vm/selectors'; import { getVMTemplateNamespacedName } from '../../selectors/vm-template/selectors'; -import { getVMStatus } from '../../statuses/vm/vm'; import { getFlavorText } from '../flavor-text'; import { EditButton } from '../edit-button'; -import { getVmiIpAddressesString } from '../ip-addresses'; import { VMStatuses } from '../vm-status'; import { DiskSummary } from '../vm-disks/disk-summary'; import { BootOrderSummary } from '../boot-order'; -import { VirtualMachineInstanceModel } from '../../models'; import { RESOURCE_PINNED, RESOURCE_NOT_PINNED, DEDICATED_RESOURCES, } from '../modals/dedicated-resources-modal/consts'; -import { - getOperatingSystemName, - getOperatingSystem, - getWorkloadProfile, - getDevices, -} from '../../selectors/vm'; -import './vm-resource.scss'; +import { getOperatingSystemName, getOperatingSystem } from '../../selectors/vm'; +import { getVmiIpAddresses } from '../../selectors/vmi/ip-address'; +import { findVMPod } from '../../selectors/pod/selectors'; import { isVMIPaused } from '../../selectors/vmi'; +import './vm-resource.scss'; +import { VirtualMachineInstanceModel, VirtualMachineModel } from '../../models'; +import { asVMILikeWrapper } from '../../k8s/wrapper/utils/convert'; + export const VMDetailsItem: React.FC = ({ title, canEdit = false, @@ -58,22 +54,32 @@ export const VMDetailsItem: React.FC = ({ ); }; -export const VMResourceSummary: React.FC = ({ vm, canUpdateVM }) => { - const templateNamespacedName = getVMTemplateNamespacedName(vm); +export const VMResourceSummary: React.FC = ({ + vm, + vmi, + canUpdateVM, + kindObj, +}) => { + const isVM = kindObj === VirtualMachineModel; + const vmiLike = isVM ? vm : vmi; - const id = getBasicID(vm); - const description = getDescription(vm); - const os = getOperatingSystemName(vm) || getOperatingSystem(vm); + const templateNamespacedName = getVMTemplateNamespacedName(vm); + const id = getBasicID(vmiLike); + const description = getDescription(vmiLike); + const os = getOperatingSystemName(vmiLike) || getOperatingSystem(vmiLike); return ( - + - vmDescriptionModal({ vmLikeEntity: vm })}> + {!description && Not available} + vmDescriptionModal({ vmLikeEntity: vmiLike })} + > {description} @@ -82,13 +88,15 @@ export const VMResourceSummary: React.FC = ({ vm, canUpd {os} - - {templateNamespacedName && } - + {isVM && ( + + {templateNamespacedName && } + + )} ); }; @@ -99,23 +107,33 @@ export const VMDetailsList: React.FC = ({ pods, migrations, canUpdateVM, + kindObj, }) => { const [isBootOrderModalOpen, setBootOrderModalOpen] = React.useState(false); const [isDedicatedResourcesModalOpen, setDedicatedResourcesModalOpen] = React.useState( false, ); + const isVM = kindObj === VirtualMachineModel; + const vmiLike = isVM ? vm : vmi; + const vmiLikeWrapper = asVMILikeWrapper(vmiLike); + + const canEdit = vmiLike && canUpdateVM && kindObj !== VirtualMachineInstanceModel; + const [isStatusModalOpen, setStatusModalOpen] = React.useState(false); - const id = getBasicID(vm); - const vmStatus = getVMStatus({ vm, vmi, pods, migrations }); - const { launcherPod } = vmStatus; - const cds = getCDRoms(vm); - const devices = getDevices(vm); + const launcherPod = findVMPod(vmiLike, pods); + const id = getBasicID(vmiLike); + const cds = vmiLikeWrapper?.getCDROMs() || []; + const devices = vmiLikeWrapper?.getLabeledDevices() || []; const nodeName = getNodeName(launcherPod); - const ipAddrs = getVmiIpAddressesString(vmi, vmStatus); - const workloadProfile = getWorkloadProfile(vm); - const flavorText = getFlavorText(vm); - const isCPUPinned = isDedicatedCPUPlacement(vm); + const ipAddrs = getVmiIpAddresses(vmi).join(', '); + const workloadProfile = vmiLikeWrapper?.getWorkloadProfile(); + const flavorText = getFlavorText({ + memory: vmiLikeWrapper?.getMemory(), + cpu: vmiLikeWrapper?.getCPU(), + flavor: vmiLikeWrapper?.getFlavor(), + }); + const isCPUPinned = vmiLikeWrapper?.isDedicatedCPUPlacement(); return (
@@ -130,14 +148,6 @@ export const VMDetailsList: React.FC = ({ - - - - {launcherPod && ( = ({ setBootOrderModalOpen(true)} idValue={prefixedID(id, 'boot-order')} @@ -165,7 +175,7 @@ export const VMDetailsList: React.FC = ({ VMCDRomModal({ vmLikeEntity: vm, modalClassName: 'modal-lg' })} idValue={prefixedID(id, 'cdrom')} @@ -187,19 +197,23 @@ export const VMDetailsList: React.FC = ({ - vmFlavorModal({ vmLike: vm })} - > - {flavorText} - + {canEdit ? ( + vmFlavorModal({ vmLike: vm })} + > + {flavorText} + + ) : ( + <>{flavorText} + )} setDedicatedResourcesModalOpen(true)} editButtonId={prefixedID(id, 'dedicated-resources-edit')} > @@ -234,12 +248,15 @@ type VMDetailsItemProps = { }; type VMResourceSummaryProps = { - vm: VMKind; + kindObj: K8sKind; + vm?: VMKind; + vmi?: VMIKind; canUpdateVM: boolean; }; type VMResourceListProps = { - vm: VMKind; + kindObj: K8sKind; + vm?: VMKind; pods?: PodKind[]; migrations?: any[]; vmi?: VMIKind; diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/vm.tsx b/frontend/packages/kubevirt-plugin/src/components/vms/vm.tsx index 465e70a8fd6..84f44467c09 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vms/vm.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vms/vm.tsx @@ -11,14 +11,12 @@ import { K8sEntityMap, dimensifyHeader, dimensifyRow, - Status, } from '@console/shared'; import { NamespaceModel, PodModel, NodeModel } from '@console/internal/models'; import { Table, MultiListPage, TableRow, TableData } from '@console/internal/components/factory'; import { FirehoseResult, Kebab, ResourceLink } from '@console/internal/components/utils'; import { fromNow } from '@console/internal/components/utils/datetime'; import { K8sResourceKind, PodKind } from '@console/internal/module/k8s'; -import { getPhase } from '@console/noobaa-storage-plugin/src/utils'; import { VMStatus } from '../vm-status/vm-status'; import { VirtualMachineInstanceMigrationModel, @@ -31,11 +29,12 @@ import { getBasicID, getLoadedData, getResource } from '../../utils'; import { getVMStatus, VMStatus as VMStatusType } from '../../statuses/vm/vm'; import { getVMStatusSortString } from '../../statuses/vm/constants'; import { getVmiIpAddresses, getVMINodeName } from '../../selectors/vmi'; -import { isVM } from '../../selectors/vm'; +import { isVM, getVMLikeModel } from '../../selectors/vm'; import { vmStatusFilter } from './table-filters'; import { vmMenuActions, vmiMenuActions } from './menu-actions'; import './vm.scss'; +import { VMILikeEntityKind } from '../../types/vmLike'; const tableColumnClasses = [ classNames('col-lg-2', 'col-md-2', 'col-sm-6', 'col-xs-6'), @@ -96,11 +95,6 @@ const VMRow: React.FC = ({ const { name, namespace, node, creationTimestamp, uid, vmStatus } = obj.metadata; const dimensify = dimensifyRow(tableColumnClasses); - const status = vm ? ( - - ) : ( - - ); const options = vm ? vmMenuActions.map((action) => action(VirtualMachineModel, vm, { @@ -114,20 +108,14 @@ const VMRow: React.FC = ({ return ( - {vm ? ( - - ) : ( - - )} + - {status} + + + {fromNow(creationTimestamp)} {node && } @@ -217,21 +205,20 @@ export const VirtualMachinesPage: React.FC = (props) = ); const virtualMachines = _.unionBy(loadedVMs, loadedVMIs, getBasicID); - return virtualMachines.map((obj: VMKind | VMIKind) => { + return virtualMachines.map((obj: VMILikeEntityKind) => { const lookupID = getBasicID(obj); const { vm, vmi } = isVM(obj) ? { vm: obj as VMKind, vmi: vmisLookup[lookupID] as VMIKind } : { vm: null, vmi: obj as VMIKind }; - const vmStatus = - vm && getVMStatus({ vm, vmi, pods: loadedPods, migrations: loadedMigrations }); + const vmStatus = getVMStatus({ vm, vmi, pods: loadedPods, migrations: loadedMigrations }); return { metadata: { name: getName(obj), namespace: getNamespace(obj), vmStatus, - status: vm ? getVMStatusSortString(vmStatus) : getPhase(vmi), + status: getVMStatusSortString(vmStatus), node: getVMINodeName(vmi), creationTimestamp: getCreationTimestamp(obj), uid: getUID(obj), diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/vmi-details-page.scss b/frontend/packages/kubevirt-plugin/src/components/vms/vmi-details-page.scss new file mode 100644 index 00000000000..c0d26e7f6b6 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/vms/vmi-details-page.scss @@ -0,0 +1,3 @@ +.kubevirt-details-page__hint-block { + margin-bottom: var(--pf-global--spacer--lg); +} diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/vmi-details-page.tsx b/frontend/packages/kubevirt-plugin/src/components/vms/vmi-details-page.tsx new file mode 100644 index 00000000000..9684eb6c8ba --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/vms/vmi-details-page.tsx @@ -0,0 +1,125 @@ +import * as React from 'react'; +import { navFactory, HintBlock, ExternalLink } from '@console/internal/components/utils'; +import { DetailsPage } from '@console/internal/components/factory'; +import { PodModel } from '@console/internal/models'; +import { VMDisksFirehose } from '../vm-disks'; +import { VMNics } from '../vm-nics'; +import { + VirtualMachineInstanceMigrationModel, + VirtualMachineInstanceModel, + VirtualMachineModel, +} from '../../models'; +import { getResource } from '../../utils'; +import { + VM_DETAIL_OVERVIEW_HREF, + VM_DETAIL_DISKS_HREF, + VM_DETAIL_NETWORKS_HREF, + VM_DETAIL_CONSOLES_HREF, +} from '../../constants'; +import { VMEvents } from './vm-events'; +import { VMConsoleFirehose } from './vm-console'; +import { VMDetailsFirehose } from './vm-details'; +import { vmiMenuActionsCreator } from './menu-actions'; +import { VMDashboard } from './vm-dashboard'; + +import './vmi-details-page.scss'; + +export const VirtualMachinesInstanceDetailsPage: React.FC = ( + props, +) => { + const { name, ns: namespace } = props.match.params; + + const breadcrumbsForVMPage = (match: any) => () => [ + { + name: VirtualMachineModel.labelPlural, + path: `/k8s/ns/${match.params.ns || 'default'}/virtualmachines`, + }, + { name: `${match.params.name} Details`, path: `${match.url}` }, + ]; + + const dashboardPage = { + href: '', // default landing page + name: 'Dashboard', + component: VMDashboard, + }; + + const overviewPage = { + href: VM_DETAIL_OVERVIEW_HREF, + name: 'Overview', + component: VMDetailsFirehose, + }; + + const consolePage = { + href: VM_DETAIL_CONSOLES_HREF, + name: 'Consoles', + component: VMConsoleFirehose, + }; + + const nicsPage = { + href: VM_DETAIL_NETWORKS_HREF, + name: 'Network Interfaces', + component: VMNics, + }; + + const disksPage = { + href: VM_DETAIL_DISKS_HREF, + name: 'Disks', + component: VMDisksFirehose, + }; + + const pages = [ + dashboardPage, + overviewPage, + navFactory.editYaml(), + consolePage, + navFactory.events(VMEvents), + nicsPage, + disksPage, + ]; + + const resources = [ + getResource(VirtualMachineModel, { + name, + namespace, + isList: false, + prop: 'vm', + optional: true, + }), + getResource(PodModel, { namespace, prop: 'pods' }), + getResource(VirtualMachineInstanceMigrationModel, { namespace, prop: 'migrations' }), + ]; + + return ( + + +

+ This is a VirtualMachineInstance overview page. Please consider using a VirtualMachine + that will provide additional management capabilities to a VirtualMachineInstance inside + the cluster. +

+ +
+
+ ); +}; + +export type VirtualMachinesInstanceDetailsPageProps = { + match: any; +}; diff --git a/frontend/packages/kubevirt-plugin/src/constants/vm/provision-source.ts b/frontend/packages/kubevirt-plugin/src/constants/vm/provision-source.ts index 50f0f011c9c..fb9c39589fc 100644 --- a/frontend/packages/kubevirt-plugin/src/constants/vm/provision-source.ts +++ b/frontend/packages/kubevirt-plugin/src/constants/vm/provision-source.ts @@ -9,7 +9,7 @@ import { getVolumeDataVolumeName, getVolumes, } from '../../selectors/vm'; -import { VMLikeEntityKind } from '../../types'; +import { VMLikeEntityKind } from '../../types/vmLike'; import { StorageUISource } from '../../components/modals/disk-modal/storage-ui-source'; import { VolumeWrapper } from '../../k8s/wrapper/vm/volume-wrapper'; import { DataVolumeWrapper } from '../../k8s/wrapper/vm/data-volume-wrapper'; diff --git a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm-template/index.ts b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm-template/index.ts index 6257d61da31..65f3d670a54 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm-template/index.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm-template/index.ts @@ -1,8 +1,9 @@ import * as _ from 'lodash'; import { TemplateKind, Patch } from '@console/internal/module/k8s'; -import { VMLikeEntityKind, VMKind } from '../../../types'; +import { VMLikeEntityKind } from '../../../types/vmLike'; +import { VMKind } from '../../../types/vm'; import { isVM } from '../../../selectors/vm'; -import { selectVM } from '../../../selectors/vm-template/selectors'; +import { selectVM } from '../../../selectors/vm-template/basic'; export const addPrefixToPatch = (prefix: string, patch: Patch): Patch => ({ ...patch, diff --git a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-cdrom-patches.ts b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-cdrom-patches.ts index 84df45c2a22..8e88630b1a3 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-cdrom-patches.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-cdrom-patches.ts @@ -2,14 +2,14 @@ import { last, includes } from 'lodash'; import { getName } from '@console/shared'; import { Volume, k8sGet } from '@console/internal/module/k8s'; import { PatchBuilder, PatchOperation } from '@console/shared/src/k8s'; -import { CD, StorageType } from '../../../components/modals/cdrom-vm-modal/constants'; +import { StorageType } from '../../../components/modals/cdrom-vm-modal/constants'; import { DataVolumeWrapper } from '../../wrapper/vm/data-volume-wrapper'; import { getDefaultSCAccessMode, getDefaultSCVolumeMode, } from '../../../selectors/config-map/sc-defaults'; import { getStorageClassConfigMap } from '../../requests/config-map/storage-class'; -import { VMLikeEntityKind } from '../../../types'; +import { VMLikeEntityKind } from '../../../types/vmLike'; import { getVolumes, getDataVolumeTemplates, @@ -20,6 +20,7 @@ import { } from '../../../selectors/vm'; import { getVMLikePatches } from '../vm-template'; import { BOOT_ORDER_FIRST, BOOT_ORDER_SECOND } from '../../../constants'; +import { CD } from '../../../components/modals/cdrom-vm-modal/types'; const getNextAvailableBootOrderIndex = (vm: VMLikeEntityKind) => { const sortedBootableDevices = getBootableDevicesInOrder(vm); diff --git a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-cpu-patches.ts b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-cpu-patches.ts index f27391ccadd..9be2e4d933b 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-cpu-patches.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-cpu-patches.ts @@ -1,6 +1,6 @@ import { PatchBuilder, PatchOperation } from '@console/shared/src/k8s'; import { Patch } from '@console/internal/module/k8s'; -import { VMLikeEntityKind } from '../../../types'; +import { VMLikeEntityKind } from '../../../types/vmLike'; import { getVMLikePatches } from '../vm-template'; import { getCPU, diff --git a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-disk-patches.ts b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-disk-patches.ts index f8475223142..a07ddef5878 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-disk-patches.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-disk-patches.ts @@ -9,7 +9,7 @@ import { getVolumes, } from '../../../selectors/vm'; import { getVMLikePatches } from '../vm-template'; -import { VMLikeEntityKind } from '../../../types'; +import { VMLikeEntityKind } from '../../../types/vmLike'; import { getSimpleName } from '../../../selectors/utils'; import { DiskWrapper } from '../../wrapper/vm/disk-wrapper'; import { V1Disk } from '../../../types/vm/disk/V1Disk'; diff --git a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-nic-patches.ts b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-nic-patches.ts index f3f5f537ad8..708963c2d09 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-nic-patches.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/patches/vm/vm-nic-patches.ts @@ -3,7 +3,8 @@ import { Patch } from '@console/internal/module/k8s'; import { PatchBuilder, PatchOperation } from '@console/shared/src/k8s'; import { getDisks, getInterfaces, getNetworks } from '../../../selectors/vm'; import { getVMLikePatches } from '../vm-template'; -import { V1Network, V1NetworkInterface, VMLikeEntityKind } from '../../../types'; +import { VMLikeEntityKind } from '../../../types/vmLike'; +import { V1Network, V1NetworkInterface } from '../../../types/vm'; import { getSimpleName } from '../../../selectors/utils'; import { NetworkWrapper } from '../../wrapper/vm/network-wrapper'; import { NetworkInterfaceWrapper } from '../../wrapper/vm/network-interface-wrapper'; 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 7e7c9b60698..34bc369052c 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,12 +1,14 @@ import * as _ from 'lodash'; import { Patch, TemplateKind } from '@console/internal/module/k8s'; -import { VMLikeEntityKind, VMKind, CPU } from '../../../types'; +import { VMGenericLikeEntityKind, VMLikeEntityKind } from '../../../types/vmLike'; import { getAnnotations, getDescription } from '../../../selectors/selectors'; 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 { getTemplateForFlavor } from '../../../selectors/vm-template/selectors'; import { getVMLikePatches } from '../vm-template'; import { isCPUEqual } from '../../../utils'; +import { selectVM } from '../../../selectors/vm-template/basic'; +import { CPU, VMKind } from '../../../types/vm'; const getLabelsPatch = (vmLike: VMLikeEntityKind): Patch => { if (!_.has(vmLike.metadata, 'labels')) { @@ -155,7 +157,7 @@ const getUpdateCpuMemoryPatch = (vm: VMKind, cpu: CPU, memory: string): Patch[] }; export const getUpdateDescriptionPatches = ( - vmLikeEntity: VMLikeEntityKind, + vmLikeEntity: VMGenericLikeEntityKind, description: string, ): Patch[] => { const patches = []; diff --git a/frontend/packages/kubevirt-plugin/src/k8s/requests/vm/create/common.ts b/frontend/packages/kubevirt-plugin/src/k8s/requests/vm/create/common.ts index 3578bb9c799..1766ed06576 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/requests/vm/create/common.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/requests/vm/create/common.ts @@ -24,12 +24,12 @@ import { operatingSystemsNative } from '../../../../components/create-vm-wizard/ import { concatImmutableLists, immutableListToShallowJS } from '../../../../utils/immutable'; import { CreateVMEnhancedParams } from './types'; -export const initializeCommonMetadata = ( - { vmSettings, iUserTemplates, openshiftFlag, iCommonTemplates }: CreateVMEnhancedParams, - entity: MutableVMWrapper | MutableVMTemplateWrapper, - template?: TemplateKind, -) => { - const settings = asSimpleSettings(vmSettings); +export const getOS = ({ + vmSettings, + iUserTemplates, + openshiftFlag, + iCommonTemplates, +}: CreateVMEnhancedParams) => { const operatingSystems = openshiftFlag ? getTemplateOperatingSystems( immutableListToShallowJS( @@ -37,8 +37,21 @@ export const initializeCommonMetadata = ( ), ) : operatingSystemsNative; - const osID = settings[VMSettingsField.OPERATING_SYSTEM]; - const osName = (operatingSystems.find(({ id }) => id === osID) || {}).name; + + const osID = getFieldValue(vmSettings, VMSettingsField.OPERATING_SYSTEM); + return { + osID, + osName: (operatingSystems.find(({ id }) => id === osID) || {}).name, + }; +}; + +export const initializeCommonMetadata = ( + createVMParams: CreateVMEnhancedParams, + entity: MutableVMWrapper | MutableVMTemplateWrapper, + template?: TemplateKind, +) => { + const settings = asSimpleSettings(createVMParams.vmSettings); + const { osID, osName } = getOS(createVMParams); entity.addAnotation(`${TEMPLATE_OS_NAME_ANNOTATION}/${osID}`, osName); @@ -65,10 +78,11 @@ export const initializeCommonMetadata = ( }; export const initializeCommonVMMetadata = ( - { vmSettings }: CreateVMEnhancedParams, + createVMParams: CreateVMEnhancedParams, entity: MutableVMWrapper, ) => { - const name = getFieldValue(vmSettings, VMSettingsField.NAME); + const settings = asSimpleSettings(createVMParams.vmSettings); + const name = settings[VMSettingsField.NAME]; entity.addTemplateLabel(TEMPLATE_VM_NAME_LABEL, name); // for pairing service-vm (like for RDP) @@ -79,4 +93,17 @@ export const initializeCommonVMMetadata = ( if (!entity.hasLabel(APP)) { entity.addLabel(APP, name); } + + // show metadata inside a VMI + const { osID } = getOS(createVMParams); + + entity.addTemplateLabel(`${TEMPLATE_OS_LABEL}/${osID}`, 'true'); + entity.addTemplateLabel(`${TEMPLATE_FLAVOR_LABEL}/${settings[VMSettingsField.FLAVOR]}`, 'true'); + + if (settings[VMSettingsField.WORKLOAD_PROFILE]) { + entity.addTemplateLabel( + `${TEMPLATE_WORKLOAD_LABEL}/${settings[VMSettingsField.WORKLOAD_PROFILE]}`, + 'true', + ); + } }; diff --git a/frontend/packages/kubevirt-plugin/src/k8s/requests/vm/create/create.ts b/frontend/packages/kubevirt-plugin/src/k8s/requests/vm/create/create.ts index 8c5897a9d3a..4257dfddbc6 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/requests/vm/create/create.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/requests/vm/create/create.ts @@ -18,7 +18,6 @@ import { import { DataVolumeModel, VirtualMachineModel } from '../../../../models'; import { MutableDataVolumeWrapper } from '../../../wrapper/vm/data-volume-wrapper'; import { buildOwnerReference } from '../../../../utils'; -import { selectVM } from '../../../../selectors/vm-template/selectors'; import { MutableVMWrapper } from '../../../wrapper/vm/vm-wrapper'; import { ProcessedTemplatesModel } from '../../../../models/models'; import { toShallowJS } from '../../../../utils/immutable'; @@ -26,6 +25,7 @@ import { iGetRelevantTemplate } from '../../../../selectors/immutable/template/c import { CreateVMEnhancedParams, CreateVMParams } from './types'; import { initializeVM } from './initialize-vm'; import { initializeCommonMetadata, initializeCommonVMMetadata } from './common'; +import { selectVM } from '../../../../selectors/vm-template/basic'; export const getInitializedVMTemplate = (params: CreateVMEnhancedParams) => { const { vmSettings, iCommonTemplates, iUserTemplates } = params; diff --git a/frontend/packages/kubevirt-plugin/src/k8s/wrapper/common/k8s-resource-wrapper.ts b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/common/k8s-resource-wrapper.ts new file mode 100644 index 00000000000..2cf5e9136b7 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/common/k8s-resource-wrapper.ts @@ -0,0 +1,10 @@ +/* eslint-disable lines-between-class-members */ +import { getName, hasLabel, getLabels } from '@console/shared/src'; +import { K8sResourceKind } from '@console/internal/module/k8s'; +import { Wrapper } from './wrapper'; + +export class K8sResourceWrapper extends Wrapper { + getName = () => getName(this.data); + getLabels = (defaultValue = {}) => getLabels(this.data, defaultValue); + hasLabel = (label: string) => hasLabel(this.data, label); +} diff --git a/frontend/packages/kubevirt-plugin/src/k8s/wrapper/types/types.ts b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/types/types.ts new file mode 100644 index 00000000000..570348a33f6 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/types/types.ts @@ -0,0 +1,9 @@ +import { K8sResourceKind } from '@console/internal/module/k8s'; + +export interface K8sResourceKindMethods { + getName: () => string; + getLabels: ( + defaultValue: K8sResourceKind['metadata']['labels'], + ) => K8sResourceKind['metadata']['labels']; + hasLabel: (label: string) => boolean; +} diff --git a/frontend/packages/kubevirt-plugin/src/k8s/wrapper/types/vmlike.ts b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/types/vmlike.ts new file mode 100644 index 00000000000..29d78c4526d --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/types/vmlike.ts @@ -0,0 +1,4 @@ +import { VMWrapper } from '../vm/vm-wrapper'; +import { VMIWrapper } from '../vm/vmi-wrapper'; + +export type VMILikeWrapper = VMWrapper | VMIWrapper; diff --git a/frontend/packages/kubevirt-plugin/src/k8s/wrapper/utils/convert.ts b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/utils/convert.ts new file mode 100644 index 00000000000..3e05dea8682 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/utils/convert.ts @@ -0,0 +1,17 @@ +import { VMGenericLikeEntityKind } from '../../../types/vmLike'; +import { VMWrapper } from '../vm/vm-wrapper'; +import { VMIWrapper } from '../vm/vmi-wrapper'; +import { asVM, isVMI } from '../../../selectors/vm/vmlike'; +import { VMILikeWrapper } from '../types/vmlike'; + +export const asVMILikeWrapper = (vmLikeEntity: VMGenericLikeEntityKind): VMILikeWrapper => { + if (!vmLikeEntity) { + return null; + } + + if (isVMI(vmLikeEntity)) { + return VMIWrapper.initialize(vmLikeEntity); + } + + return VMWrapper.initialize(asVM(vmLikeEntity)); +}; diff --git a/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/combined-disk.ts b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/combined-disk.ts index a135c310eed..0f2c7bc9959 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/combined-disk.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/combined-disk.ts @@ -7,14 +7,8 @@ import { V1Volume } from '../../../types/vm/disk/V1Volume'; import { V1alpha1DataVolume } from '../../../types/vm/disk/V1alpha1DataVolume'; import { getSimpleName } from '../../../selectors/utils'; import { VolumeType, DiskType } from '../../../constants/vm/storage'; -import { VMLikeEntityKind } from '../../../types'; -import { - asVM, - getDataVolumeTemplates, - getDisks, - getVolumes, - isWinToolsImage, -} from '../../../selectors/vm'; +import { VMGenericLikeEntityKind } from '../../../types/vmLike'; +import { asVM, getDataVolumeTemplates, isWinToolsImage } from '../../../selectors/vm'; import { getLoadedData, isLoaded } from '../../../utils'; import { StorageUISource } from '../../../components/modals/disk-modal/storage-ui-source'; import { DYNAMIC } from '../../../utils/strings'; @@ -22,6 +16,7 @@ import { DiskWrapper } from './disk-wrapper'; import { DataVolumeWrapper } from './data-volume-wrapper'; import { VolumeWrapper } from './volume-wrapper'; import { PersistentVolumeClaimWrapper } from './persistent-volume-claim-wrapper'; +import { asVMILikeWrapper } from '../utils/convert'; export class CombinedDisk { private readonly dataVolumesLoading: boolean; @@ -217,16 +212,19 @@ export class CombinedDiskFactory { private readonly pvcsLoading: boolean; static initializeFromVMLikeEntity = ( - vmLikeEntity: VMLikeEntityKind, + vmLikeEntity: VMGenericLikeEntityKind, datavolumes?: FirehoseResult, - pvcs?: FirehoseResult, + pvcs?: FirehoseResult, ) => { - const vm = asVM(vmLikeEntity); + const vmiLikeWrapper = asVMILikeWrapper(vmLikeEntity); return new CombinedDiskFactory({ - disks: getDisks(vm), - volumes: getVolumes(vm), - dataVolumes: [...getLoadedData(datavolumes, []), ...getDataVolumeTemplates(vm)], + disks: vmiLikeWrapper?.getDisks() || [], + volumes: vmiLikeWrapper?.getVolumes() || [], + dataVolumes: [ + ...getLoadedData(datavolumes, []), + ...getDataVolumeTemplates(asVM(vmLikeEntity)), + ], pvcs: getLoadedData(pvcs), dataVolumesLoading: !isLoaded(datavolumes), pvcsLoading: !isLoaded(pvcs), diff --git a/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/types.ts b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/types.ts new file mode 100644 index 00000000000..e971759998c --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/types.ts @@ -0,0 +1,31 @@ +import { CPURaw, V1Network, V1NetworkInterface } from '../../../types/vm'; +import { V1Disk } from '../../../types/vm/disk/V1Disk'; +import { V1Volume } from '../../../types/vm/disk/V1Volume'; +import { K8sResourceKindMethods } from '../types/types'; +import { BootableDeviceType } from '../../../types'; + +export interface VMILikeMethods extends K8sResourceKindMethods { + getInterfaces: (defaultValue: V1NetworkInterface[]) => V1NetworkInterface[]; + + getDisks: (defaultValue: V1Disk[]) => V1Disk[]; + + getCDROMs: (defaultValue: V1Disk[]) => V1Disk[]; + + getNetworks: (defaultValue: V1Network[]) => V1Network[]; + + getVolumes: (defaultValue: V1Volume[]) => V1Volume[]; + + getLabeledDevices: () => BootableDeviceType[]; + + isDedicatedCPUPlacement: () => boolean; + + getOperatingSystem: () => string; + + getWorkloadProfile: () => string; + + getFlavor: () => string; + + getMemory: () => string; + + getCPU: () => CPURaw; +} diff --git a/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/vm-template-wrapper.ts b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/vm-template-wrapper.ts index 25202931018..79ecebebe97 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/vm-template-wrapper.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/vm-template-wrapper.ts @@ -1,15 +1,20 @@ /* eslint-disable lines-between-class-members */ -import * as _ from 'lodash'; -import { getName } from '@console/shared/src'; import { apiVersionForModel, K8sKind, TemplateKind } from '@console/internal/module/k8s'; import { TemplateModel } from '@console/internal/models'; -import { Wrapper } from '../common/wrapper'; -import { getLabels } from '../../../selectors/selectors'; +import { K8sResourceWrapper } from '../common/k8s-resource-wrapper'; import { ensurePath } from '../utils/utils'; -import { selectVM } from '../../../selectors/vm-template/selectors'; import { MutableVMWrapper, VMWrapper } from './vm-wrapper'; - -export class VMTemplateWrapper extends Wrapper { +import { K8sResourceKindMethods } from '../types/types'; +import { selectVM } from '../../../selectors/vm-template/basic'; +import { findKeySuffixValue } from '../../../selectors/utils'; +import { + TEMPLATE_FLAVOR_LABEL, + TEMPLATE_OS_LABEL, + TEMPLATE_WORKLOAD_LABEL, +} from '../../../constants/vm'; + +export class VMTemplateWrapper extends K8sResourceWrapper + implements K8sResourceKindMethods { static mergeWrappers = (...vmTemplateWrappers: VMTemplateWrapper[]): VMTemplateWrapper => VMTemplateWrapper.defaultMergeWrappers(VMTemplateWrapper, vmTemplateWrappers); @@ -47,9 +52,9 @@ export class VMTemplateWrapper extends Wrapper { super(vmTemplate, opts); } - getName = () => getName(this.data); - getLabels = (defaultValue = {}) => getLabels(this.data, defaultValue); - hasLabel = (label: string) => _.has(this.getLabels(null), label); + getOperatingSystem = () => findKeySuffixValue(this.getLabels(), TEMPLATE_OS_LABEL); + getWorkloadProfile = () => findKeySuffixValue(this.getLabels(), TEMPLATE_WORKLOAD_LABEL); + getFlavor = () => findKeySuffixValue(this.getLabels(), TEMPLATE_FLAVOR_LABEL); getParameters = (defaultValue = []) => (this.data && this.data.parameters) || defaultValue; diff --git a/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/vm-wrapper.ts b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/vm-wrapper.ts index bfe7d289cd6..143a21b151a 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/vm-wrapper.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/vm-wrapper.ts @@ -1,23 +1,31 @@ /* eslint-disable lines-between-class-members */ import * as _ from 'lodash'; -import { getName } from '@console/shared/src'; +import { getLabels } from '@console/shared/src'; import { apiVersionForModel, K8sKind } from '@console/internal/module/k8s'; -import { Wrapper } from '../common/wrapper'; -import { VMKind } from '../../../types/vm'; +import { K8sResourceWrapper } from '../common/k8s-resource-wrapper'; +import { CPURaw, VMKind } from '../../../types/vm'; import { getDataVolumeTemplates, getDisks, getInterfaces, getNetworks, getVolumes, -} from '../../../selectors/vm'; -import { getLabels } from '../../../selectors/selectors'; + isDedicatedCPUPlacement, +} from '../../../selectors/vm/selectors'; import { ensurePath } from '../utils/utils'; import { VMWizardNetwork, VMWizardStorage } from '../../../components/create-vm-wizard/types'; +import { VMILikeMethods } from './types'; +import { transformDevices } from '../../../selectors/vm'; +import { findKeySuffixValue } from '../../../selectors/utils'; +import { + TEMPLATE_FLAVOR_LABEL, + TEMPLATE_OS_LABEL, + TEMPLATE_WORKLOAD_LABEL, +} from '../../../constants/vm'; -export class VMWrapper extends Wrapper { +export class VMWrapper extends K8sResourceWrapper implements VMILikeMethods { static mergeWrappers = (...vmWrappers: VMWrapper[]): VMWrapper => - Wrapper.defaultMergeWrappers(VMWrapper, vmWrappers); + K8sResourceWrapper.defaultMergeWrappers(VMWrapper, vmWrappers); static initialize = (vm?: VMKind, copy?: boolean) => new VMWrapper(vm, copy && { copy }); @@ -30,11 +38,15 @@ export class VMWrapper extends Wrapper { super(vm, opts); } - getName = () => getName(this.data); - getLabels = (defaultValue = {}) => getLabels(this.data, defaultValue); - hasLabel = (label: string) => _.has(this.getLabels(null), label); hasTemplateLabel = (label: string) => _.has(this.getTemplateLabels(null), label); + getOperatingSystem = () => findKeySuffixValue(this.getLabels(), TEMPLATE_OS_LABEL); + getWorkloadProfile = () => findKeySuffixValue(this.getLabels(), TEMPLATE_WORKLOAD_LABEL); + getFlavor = () => findKeySuffixValue(this.getLabels(), TEMPLATE_FLAVOR_LABEL); + + getMemory = () => this.data?.spec?.template?.spec?.domain?.resources?.requests?.memory; + getCPU = (): CPURaw => this.data?.spec?.template?.spec?.domain?.cpu; + getTemplateLabels = (defaultValue = {}) => getLabels(_.get(this.data, 'spec.template'), defaultValue); @@ -43,10 +55,15 @@ export class VMWrapper extends Wrapper { getInterfaces = (defaultValue = []) => getInterfaces(this.data, defaultValue); getDisks = (defaultValue = []) => getDisks(this.data, defaultValue); + getCDROMs = () => this.getDisks().filter((device) => !!device.cdrom); getNetworks = (defaultValue = []) => getNetworks(this.data, defaultValue); getVolumes = (defaultValue = []) => getVolumes(this.data, defaultValue); + + getLabeledDevices = () => transformDevices(this.getDisks(), this.getInterfaces()); + + isDedicatedCPUPlacement = () => isDedicatedCPUPlacement(this.data); } export class MutableVMWrapper extends VMWrapper { @@ -96,6 +113,14 @@ export class MutableVMWrapper extends VMWrapper { return this; }; + addTemplateAnnotation = (key: string, value: string) => { + if (key) { + this.ensurePath('spec.template.metadata.annotations', {}); + this.data.spec.template.metadata.annotations[key] = value; + } + return this; + }; + setMemory = (value: string, unit = 'Gi') => { this.ensurePath('spec.template.spec.domain.resources.requests', {}); this.data.spec.template.spec.domain.resources.requests.memory = `${value}${unit}`; diff --git a/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/vmi-wrapper.ts b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/vmi-wrapper.ts new file mode 100644 index 00000000000..fba1ee8fade --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/vmi-wrapper.ts @@ -0,0 +1,53 @@ +/* eslint-disable lines-between-class-members */ +import { CPURaw, VMIKind } from '../../../types/vm'; +import { K8sResourceWrapper } from '../common/k8s-resource-wrapper'; +import { + getVMIDisks, + getVMINetworks, + getVMIVolumes, + getVMIInterfaces, +} from '../../../selectors/vmi'; +import { VMILikeMethods } from './types'; +import { transformDevices } from '../../../selectors/vm'; +import { findKeySuffixValue } from '../../../selectors/utils'; +import { + TEMPLATE_FLAVOR_LABEL, + TEMPLATE_OS_LABEL, + TEMPLATE_WORKLOAD_LABEL, +} from '../../../constants/vm'; + +export class VMIWrapper extends K8sResourceWrapper implements VMILikeMethods { + static mergeWrappers = (...vmiWrappers: VMIWrapper[]): VMIWrapper => + K8sResourceWrapper.defaultMergeWrappers(VMIWrapper, vmiWrappers); + + static initialize = (vm?: VMIKind, copy?: boolean) => new VMIWrapper(vm, copy && { copy }); + + protected constructor( + vm?: VMIKind, + opts?: { + copy?: boolean; + }, + ) { + super(vm, opts); + } + + getOperatingSystem = () => findKeySuffixValue(this.getLabels(), TEMPLATE_OS_LABEL); + getWorkloadProfile = () => findKeySuffixValue(this.getLabels(), TEMPLATE_WORKLOAD_LABEL); + getFlavor = () => findKeySuffixValue(this.getLabels(), TEMPLATE_FLAVOR_LABEL); + + getMemory = () => this.data?.spec?.domain?.resources?.requests?.memory; + getCPU = (): CPURaw => this.data?.spec?.domain?.cpu; + + getInterfaces = (defaultValue = []) => getVMIInterfaces(this.data, defaultValue); + + getDisks = (defaultValue = []) => getVMIDisks(this.data, defaultValue); + getCDROMs = () => this.getDisks().filter((device) => !!device.cdrom); + + getNetworks = (defaultValue = []) => getVMINetworks(this.data, defaultValue); + + getVolumes = (defaultValue = []) => getVMIVolumes(this.data, defaultValue); + + getLabeledDevices = () => transformDevices(this.getDisks(), this.getInterfaces()); + + isDedicatedCPUPlacement = () => this.data.spec?.domain?.cpu?.dedicatedCpuPlacement || false; +} diff --git a/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/volume-wrapper.ts b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/volume-wrapper.ts index 7d865622157..144fe6f7862 100644 --- a/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/volume-wrapper.ts +++ b/frontend/packages/kubevirt-plugin/src/k8s/wrapper/vm/volume-wrapper.ts @@ -6,7 +6,7 @@ import { getVolumeContainerImage, getVolumeDataVolumeName, getVolumePersistentVolumeClaimName, -} from '../../../selectors/vm'; +} from '../../../selectors/vm/volume'; type CombinedTypeData = { name?: string; diff --git a/frontend/packages/kubevirt-plugin/src/plugin.tsx b/frontend/packages/kubevirt-plugin/src/plugin.tsx index 879c81da81a..eb96df761ed 100644 --- a/frontend/packages/kubevirt-plugin/src/plugin.tsx +++ b/frontend/packages/kubevirt-plugin/src/plugin.tsx @@ -120,9 +120,33 @@ const plugin: Plugin = [ }, }, { - type: 'Page/Resource/Details', + type: 'Page/Route', properties: { - model: models.VirtualMachineModel, + exact: true, + path: ['/k8s/ns/:ns/virtualmachines/~new'], + loader: () => + import('./components/vms/vm-create-yaml' /* webpackChunkName: "kubevirt" */).then( + (m) => m.VMCreateYAML, + ), + required: FLAG_KUBEVIRT, + }, + }, + { + type: 'Page/Route', + properties: { + exact: true, + path: ['/k8s/ns/:ns/virtualmachines/~new-wizard'], + loader: () => + import( + './components/create-vm-wizard' /* webpackChunkName: "kubevirt-create-vm-wizard" */ + ).then((m) => m.CreateVMWizardPage), + required: FLAG_KUBEVIRT, + }, + }, + { + type: 'Page/Route', + properties: { + path: '/k8s/ns/:ns/virtualmachines/:name', loader: () => import('./components/vms/vm-details-page' /* webpackChunkName: "kubevirt" */).then( (m) => m.VirtualMachinesDetailsPage, @@ -132,13 +156,11 @@ const plugin: Plugin = [ { type: 'Page/Route', properties: { - exact: true, - path: ['/k8s/ns/:ns/vmtemplates', '/k8s/all-namespaces/vmtemplates'], + path: '/k8s/ns/:ns/virtualmachineinstances/:name', loader: () => - import('./components/vm-templates/vm-template' /* webpackChunkName: "kubevirt" */).then( - (m) => m.VirtualMachineTemplatesPage, + import('./components/vms/vmi-details-page' /* webpackChunkName: "kubevirt" */).then( + (m) => m.VirtualMachinesInstanceDetailsPage, ), - required: FLAG_KUBEVIRT, }, }, { @@ -157,7 +179,7 @@ const plugin: Plugin = [ type: 'Page/Route', properties: { exact: true, - path: ['/k8s/ns/:ns/virtualmachines/~new-wizard'], + path: ['/k8s/ns/:ns/vmtemplates/~new-wizard'], loader: () => import( './components/create-vm-wizard' /* webpackChunkName: "kubevirt-create-vm-wizard" */ @@ -168,23 +190,23 @@ const plugin: Plugin = [ { type: 'Page/Route', properties: { - exact: true, - path: ['/k8s/ns/:ns/vmtemplates/~new-wizard'], + path: '/k8s/ns/:ns/vmtemplates/:name', loader: () => import( - './components/create-vm-wizard' /* webpackChunkName: "kubevirt-create-vm-wizard" */ - ).then((m) => m.CreateVMWizardPage), + './components/vm-templates/vm-template-details-page' /* webpackChunkName: "kubevirt" */ + ).then((m) => m.VMTemplateDetailsPage), required: FLAG_KUBEVIRT, }, }, { type: 'Page/Route', properties: { - path: '/k8s/ns/:ns/vmtemplates/:name', + exact: true, + path: ['/k8s/ns/:ns/vmtemplates', '/k8s/all-namespaces/vmtemplates'], loader: () => - import( - './components/vm-templates/vm-template-details-page' /* webpackChunkName: "kubevirt" */ - ).then((m) => m.VMTemplateDetailsPage), + import('./components/vm-templates/vm-template' /* webpackChunkName: "kubevirt" */).then( + (m) => m.VirtualMachineTemplatesPage, + ), required: FLAG_KUBEVIRT, }, }, diff --git a/frontend/packages/kubevirt-plugin/src/selectors/event/filters.ts b/frontend/packages/kubevirt-plugin/src/selectors/event/filters.ts index fa5755ee8ed..0a5785f145b 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/event/filters.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/event/filters.ts @@ -1,32 +1,40 @@ import { getName, getNamespace } from '@console/shared'; import { PodModel } from '@console/internal/models'; import { EventInvolvedObject } from '@console/internal/module/k8s'; -import { VMIKind, VMKind } from '../../types/vm'; import { VirtualMachineInstanceMigrationModel, VirtualMachineInstanceModel, VirtualMachineModel, } from '../../models'; import { VIRT_LAUNCHER_POD_PREFIX } from '../../constants/vm'; +import { VMILikeEntityKind } from '../../types/vmLike'; type EventFilterFunction = (src: EventInvolvedObject) => boolean; -const vmEventFilter = (vm: VMKind): EventFilterFunction => ({ kind, namespace, name }) => +const vmEventFilter = (vm: VMILikeEntityKind): EventFilterFunction => ({ kind, namespace, name }) => kind === VirtualMachineModel.kind && name === getName(vm) && namespace === getNamespace(vm); -const vmiEventFilter = (vm: VMKind | VMIKind): EventFilterFunction => ({ kind, namespace, name }) => +const vmiEventFilter = (vm: VMILikeEntityKind): EventFilterFunction => ({ + kind, + namespace, + name, +}) => kind === VirtualMachineInstanceModel.kind && name === getName(vm) && namespace === getNamespace(vm); -const launcherPodEventFilter = (vm: VMKind): EventFilterFunction => { +const launcherPodEventFilter = (vm: VMILikeEntityKind): EventFilterFunction => { const podNameStart = `${VIRT_LAUNCHER_POD_PREFIX}${getName(vm)}-`; return ({ kind, namespace, name }) => kind === PodModel.kind && namespace === getNamespace(vm) && name.startsWith(podNameStart); }; -const importerPodEventFilter = (vm: VMKind): EventFilterFunction => ({ kind, namespace, name }) => { +const importerPodEventFilter = (vm: VMILikeEntityKind): EventFilterFunction => ({ + kind, + namespace, + name, +}) => { // importer pod example importer--- // note: diskName and vmname may contain '-' which means pod name should have at least 4 parts if ( @@ -45,20 +53,24 @@ const importerPodEventFilter = (vm: VMKind): EventFilterFunction => ({ kind, nam return false; }; -const vmiMigrationEventFilter = (vm: VMKind): EventFilterFunction => ({ kind, namespace, name }) => +const vmiMigrationEventFilter = (vm: VMILikeEntityKind): EventFilterFunction => ({ + kind, + namespace, + name, +}) => kind === VirtualMachineInstanceMigrationModel.kind && namespace === getNamespace(vm) && name === `${getName(vm)}-migration`; // Conversion pod name example: kubevirt-v2v-conversion-[vmName]- const V2V_CONVERSION_POD_NAME_PREFIX = 'kubevirt-v2v-conversion-'; -const v2vConversionPodEventFilter = (vm: VMKind): EventFilterFunction => ({ +const v2vConversionPodEventFilter = (vm: VMILikeEntityKind): EventFilterFunction => ({ kind, namespace, name, }) => { /* Idea for improvement: - Find the conversion pod via provided event.involvedObject.uid and check it's ownerReference to VirtualMachine vm. + Find the conversion pod via provided event.involvedObject.uid and check it's ownerReference to VirtualMachine vm. This way, we would avoid false-positive matching which is possible when comparing just by name as implemented bellow. When this can happen: Conversion is started, the VM deleted and a new conversion for a VM of the same name is created again. The events will be merged together in that case. @@ -80,7 +92,7 @@ const v2vConversionPodEventFilter = (vm: VMKind): EventFilterFunction => ({ return false; }; -export const getVmEventsFilters = (vm: VMKind): EventFilterFunction[] => [ +export const getVmEventsFilters = (vm: VMILikeEntityKind): EventFilterFunction[] => [ vmiEventFilter(vm), vmEventFilter(vm), launcherPodEventFilter(vm), diff --git a/frontend/packages/kubevirt-plugin/src/selectors/pod/selectors.ts b/frontend/packages/kubevirt-plugin/src/selectors/pod/selectors.ts index 0f7e5f9b07f..7a1d0240069 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/pod/selectors.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/pod/selectors.ts @@ -12,6 +12,7 @@ import { } from '../../constants'; import { buildOwnerReferenceForModel } from '../../utils'; import { VirtualMachineInstanceModel } from '../../models'; +import { VMILikeEntityKind } from '../../types/vmLike'; export const getHostName = (pod: PodKind) => get(pod, 'spec.hostname') as PodKind['spec']['hostname']; @@ -47,7 +48,7 @@ export const isPodSchedulable = (pod: PodKind) => { }; export const findVMPod = ( - vm: VMKind, + vm: VMILikeEntityKind, pods?: PodKind[], podNamePrefix = VIRT_LAUNCHER_POD_PREFIX, ) => { diff --git a/frontend/packages/kubevirt-plugin/src/selectors/selectors.ts b/frontend/packages/kubevirt-plugin/src/selectors/selectors.ts index 9fc95cfb569..8da0d10d674 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/selectors.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/selectors.ts @@ -1,6 +1,6 @@ import * as _ from 'lodash'; import { K8sResourceKind } from '@console/internal/module/k8s'; -import { VMLikeEntityKind } from '../types'; +import { VMGenericLikeEntityKind } from '../types/vmLike'; export const getKind = (value) => _.get(value, 'kind') as K8sResourceKind['kind']; @@ -9,12 +9,15 @@ export const getGeneratedName = (value) => export const getLabels = (entity: K8sResourceKind, defaultValue?: any) => _.get(entity, 'metadata.labels', defaultValue) as K8sResourceKind['metadata']['labels']; -export const getAnnotations = (vm: VMLikeEntityKind, defaultValue?: any) => +export const getAnnotations = (vm: VMGenericLikeEntityKind, defaultValue?: any) => _.get(vm, 'metadata.annotations', defaultValue); -export const getAnnotation = (vm: VMLikeEntityKind, annotationName: string, defaultValue?: any) => - _.get(vm, ['metadata', 'annotations', annotationName], defaultValue); +export const getAnnotation = ( + vm: VMGenericLikeEntityKind, + annotationName: string, + defaultValue?: any, +) => _.get(vm, ['metadata', 'annotations', annotationName], defaultValue); -export const getDescription = (vm: VMLikeEntityKind) => +export const getDescription = (vm: VMGenericLikeEntityKind) => _.get(vm, 'metadata.annotations.description'); export const getLabelValue = (entity: K8sResourceKind, label: string): string => diff --git a/frontend/packages/kubevirt-plugin/src/selectors/vm-template/advanced.ts b/frontend/packages/kubevirt-plugin/src/selectors/vm-template/advanced.ts index b3f1d6b619c..52fcc475654 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/vm-template/advanced.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/vm-template/advanced.ts @@ -15,7 +15,7 @@ import { } from '../../constants/vm'; import { getCloudInitVolume } from '../vm/selectors'; import { VolumeWrapper } from '../../k8s/wrapper/vm/volume-wrapper'; -import { selectVM } from './selectors'; +import { selectVM } from './basic'; export const getTemplatesWithLabels = (templates: TemplateKind[], labels: string[]) => { const requiredLabels = labels.filter((label) => label); diff --git a/frontend/packages/kubevirt-plugin/src/selectors/vm-template/basic.ts b/frontend/packages/kubevirt-plugin/src/selectors/vm-template/basic.ts new file mode 100644 index 00000000000..c252646fe24 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/selectors/vm-template/basic.ts @@ -0,0 +1,8 @@ +import { TemplateKind } from '@console/internal/module/k8s'; +import { VMKind } from '../../types/vm'; +import { VirtualMachineModel } from '../../models'; + +export const selectVM = (vmTemplate: TemplateKind): VMKind => + vmTemplate && vmTemplate.objects + ? vmTemplate.objects.find((obj) => obj.kind === VirtualMachineModel.kind) + : null; 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 d21a1ed2b92..8e814b5a106 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/vm-template/selectors.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/vm-template/selectors.ts @@ -1,22 +1,22 @@ import * as _ from 'lodash'; -import { getNamespace, getName } from '@console/shared/src/selectors'; +import { getName, getNamespace } from '@console/shared/src/selectors'; import { TemplateKind } from '@console/internal/module/k8s'; -import { VirtualMachineModel } from '../../models'; -import { VMKind, VMLikeEntityKind } from '../../types'; +import { VMGenericLikeEntityKind } from '../../types/vmLike'; import { iGetIn } from '../../utils/immutable'; import { + CUSTOM_FLAVOR, TEMPLATE_FLAVOR_LABEL, TEMPLATE_OS_LABEL, - TEMPLATE_WORKLOAD_LABEL, - CUSTOM_FLAVOR, TEMPLATE_TYPE_LABEL, + TEMPLATE_WORKLOAD_LABEL, } from '../../constants'; import { getLabels } from '../selectors'; import { getOperatingSystem, getWorkloadProfile } from '../vm/selectors'; import { flavorSort } from '../../utils/sort'; +import { VMKind } from '../../types/vm'; export const getVMTemplateNamespacedName = ( - vm: VMLikeEntityKind, + vm: VMGenericLikeEntityKind, ): { name: string; namespace: string } => { if (!vm || !vm.metadata || !vm.metadata.labels) { return null; @@ -27,7 +27,7 @@ export const getVMTemplateNamespacedName = ( return name && namespace ? { name, namespace } : null; }; -const getVMTemplate = (vm: VMLikeEntityKind, templates: TemplateKind[]): TemplateKind => { +const getVMTemplate = (vm: VMGenericLikeEntityKind, templates: TemplateKind[]): TemplateKind => { const namespacedName = getVMTemplateNamespacedName(vm); return namespacedName ? templates.find( @@ -38,9 +38,6 @@ const getVMTemplate = (vm: VMLikeEntityKind, templates: TemplateKind[]): Templat : undefined; }; -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) => { @@ -105,7 +102,7 @@ export const getTemplateForFlavor = (templates: TemplateKind[], vm: VMKind, flav return matchingTemplates.length > 0 ? matchingTemplates[0] : undefined; }; -export const getFlavors = (vm: VMLikeEntityKind, templates: TemplateKind[]) => { +export const getFlavors = (vm: VMGenericLikeEntityKind, templates: TemplateKind[]) => { const vmTemplate = getVMTemplate(vm, templates); const flavors = { diff --git a/frontend/packages/kubevirt-plugin/src/selectors/vm/combined.ts b/frontend/packages/kubevirt-plugin/src/selectors/vm/combined.ts index 21455e74962..48ca12ef29a 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/vm/combined.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/vm/combined.ts @@ -12,6 +12,7 @@ import { import { OS_WINDOWS_PREFIX } from '../../constants'; import { isVMIRunning } from '../vmi/basic'; import { isVMRunning, getOperatingSystem } from './selectors'; +import { VMILikeEntityKind } from '../../types/vmLike'; const IMPORTING_STATUSES = new Set([VM_STATUS_IMPORTING, VM_STATUS_V2V_CONVERSION_IN_PROGRESS]); @@ -21,12 +22,13 @@ export const isVMImporting = (status: VMMultiStatus): boolean => export const isVMRunningWithVMI = ({ vm, vmi }: { vm: VMKind; vmi: VMIKind }): boolean => isVMRunning(vm) && !_.isEmpty(vmi); -export const isVMStarting = (vm: VMKind, vmi: VMIKind) => isVMRunning(vm) && !isVMIRunning(vmi); +export const isVMStarting = (vm: VMKind, vmi: VMIKind) => + (isVMRunning(vm) || vmi) && !isVMIRunning(vmi); export const isWindows = (vm: VMKind): boolean => (getOperatingSystem(vm) || '').startsWith(OS_WINDOWS_PREFIX); -export const findConversionPod = (vm: VMKind, pods: PodKind[]) => { +export const findConversionPod = (vm: VMILikeEntityKind, pods: PodKind[]) => { if (!pods) { return null; } diff --git a/frontend/packages/kubevirt-plugin/src/selectors/vm/devices.ts b/frontend/packages/kubevirt-plugin/src/selectors/vm/devices.ts index 3b835e8ba6e..8c29ec2882a 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/vm/devices.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/vm/devices.ts @@ -1,9 +1,11 @@ import * as _ from 'lodash'; -import { VMLikeEntityKind, BootableDeviceType } from '../../types'; +import { BootableDeviceType, V1NetworkInterface } from '../../types'; import { DiskWrapper } from '../../k8s/wrapper/vm/disk-wrapper'; import { DeviceType } from '../../constants'; import { getDisks, getInterfaces } from './selectors'; import { asVM } from './vmlike'; +import { VMLikeEntityKind } from '../../types/vmLike'; +import { V1Disk } from '../../types/vm/disk/V1Disk'; export const getBootDeviceIndex = (devices, bootOrder) => devices.findIndex((device) => device.bootOrder === bootOrder); @@ -11,23 +13,29 @@ export const getBootDeviceIndex = (devices, bootOrder) => export const getDeviceBootOrder = (device, defaultValue?): number => device && device.bootOrder === undefined ? defaultValue : device.bootOrder; -export const getDevices = (vmLikeEntity: VMLikeEntityKind): BootableDeviceType[] => { - const vm = asVM(vmLikeEntity); - - const disks = getDisks(vm).map((disk) => ({ +export const transformDevices = ( + disks: V1Disk[] = [], + nics: V1NetworkInterface[] = [], +): BootableDeviceType[] => { + const transformedDisks = disks.map((disk) => ({ type: DeviceType.DISK, typeLabel: DiskWrapper.initialize(disk) .getType() .toString(), value: disk, })); - const nics = getInterfaces(vm).map((nic) => ({ + const transformedNics = nics.map((nic) => ({ type: DeviceType.NIC, typeLabel: 'NIC', value: nic, })); - return [...disks, ...nics]; + return [...transformedDisks, ...transformedNics]; +}; + +export const getDevices = (vmLikeEntity: VMLikeEntityKind): BootableDeviceType[] => { + const vm = asVM(vmLikeEntity); + return transformDevices(getDisks(vm), getInterfaces(vm)); }; export const getBootableDevices = (vm: VMLikeEntityKind): BootableDeviceType[] => { diff --git a/frontend/packages/kubevirt-plugin/src/selectors/vm/selectors.ts b/frontend/packages/kubevirt-plugin/src/selectors/vm/selectors.ts index 5164ad3e5e0..0ad3b36e2a7 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 { V1Network, V1NetworkInterface, VMKind, VMLikeEntityKind, CPURaw } from '../../types'; +import { V1Network, V1NetworkInterface, VMKind, VMIKind, CPURaw } from '../../types'; import { findKeySuffixValue, getSimpleName, getValueByPrefix } from '../utils'; import { getAnnotations, getLabels } from '../selectors'; import { NetworkWrapper } from '../../k8s/wrapper/vm/network-wrapper'; @@ -21,6 +21,10 @@ import { getVolumeCloudInitNoCloud, } from './volume'; import { vCPUCount } from './cpu'; +import { getVMIDisks } from '../vmi/basic'; +import { VirtualMachineModel } from '../../models'; +import { V1Volume } from '../../types/vm/disk/V1Volume'; +import { VMGenericLikeEntityKind, VMILikeEntityKind } from '../../types/vmLike'; export const getMemory = (vm: VMKind) => _.get(vm, 'spec.template.spec.domain.resources.requests.memory'); @@ -31,33 +35,36 @@ export const getResourcesLimitsCPUCount = (vm: VMKind): string => vm?.spec?.template?.spec?.domain?.resources?.limits?.cpu; export const isDedicatedCPUPlacement = (vm: VMKind) => _.get(vm, 'spec.template.spec.domain.cpu.dedicatedCpuPlacement'); -export const getDisks = (vm: VMKind, defaultValue = []): V1Disk[] => +export const getDisks = (vm: VMKind, defaultValue: V1Disk[] = []): V1Disk[] => _.get(vm, 'spec.template.spec.domain.devices.disks') == null ? defaultValue : vm.spec.template.spec.domain.devices.disks; -export const getInterfaces = (vm: VMKind, defaultValue = []): V1NetworkInterface[] => +export const getInterfaces = ( + vm: VMKind, + defaultValue: V1NetworkInterface[] = [], +): V1NetworkInterface[] => _.get(vm, 'spec.template.spec.domain.devices.interfaces') == null ? defaultValue : vm.spec.template.spec.domain.devices.interfaces; -export const getNetworks = (vm: VMKind, defaultValue = []): V1Network[] => +export const getNetworks = (vm: VMKind, defaultValue: V1Network[] = []): V1Network[] => _.get(vm, 'spec.template.spec.networks') == null ? defaultValue : vm.spec.template.spec.networks; -export const getVolumes = (vm: VMKind, defaultValue = []) => +export const getVolumes = (vm: VMKind, defaultValue: V1Volume[] = []): V1Volume[] => _.get(vm, 'spec.template.spec.volumes') == null ? defaultValue : vm.spec.template.spec.volumes; export const getDataVolumeTemplates = (vm: VMKind, defaultValue = []) => _.get(vm, 'spec.dataVolumeTemplates') == null ? defaultValue : vm.spec.dataVolumeTemplates; -export const getOperatingSystem = (vmLike: VMLikeEntityKind): string => +export const getOperatingSystem = (vmLike: VMGenericLikeEntityKind): string => findKeySuffixValue(getLabels(vmLike), TEMPLATE_OS_LABEL); -export const getOperatingSystemName = (vmLike: VMLikeEntityKind) => +export const getOperatingSystemName = (vmLike: VMGenericLikeEntityKind) => getValueByPrefix( getAnnotations(vmLike), `${TEMPLATE_OS_NAME_ANNOTATION}/${getOperatingSystem(vmLike)}`, ); -export const getWorkloadProfile = (vm: VMLikeEntityKind) => +export const getWorkloadProfile = (vm: VMGenericLikeEntityKind) => findKeySuffixValue(getLabels(vm), TEMPLATE_WORKLOAD_LABEL); -export const getFlavor = (vmLike: VMLikeEntityKind) => +export const getFlavor = (vmLike: VMGenericLikeEntityKind) => findKeySuffixValue(getLabels(vmLike), TEMPLATE_FLAVOR_LABEL); export const isVMRunning = (value: VMKind) => @@ -92,7 +99,7 @@ export const getFlavorDescription = (vm: VMKind) => { return resourceStr || undefined; }; -export const getVMStatusConditions = (vm: VMKind) => +export const getVMStatusConditions = (vm: VMILikeEntityKind) => _.get(vm, 'status.conditions', []) as VMKind['status']['conditions']; export const getCloudInitVolume = (vm: VMKind) => { @@ -111,7 +118,10 @@ export const getCloudInitVolume = (vm: VMKind) => { export const hasAutoAttachPodInterface = (vm: VMKind, defaultValue = false) => _.get(vm, 'spec.template.spec.domain.devices.autoattachPodInterface', defaultValue); -export const getCDRoms = (vm: VMKind) => getDisks(vm).filter((device) => !!device.cdrom); +export const getCDRoms = (vm: VMILikeEntityKind) => + vm.kind === VirtualMachineModel.kind + ? getDisks(vm as VMKind).filter((device) => !!device.cdrom) + : getVMIDisks(vm as VMIKind).filter((device) => !!device.cdrom); export const getContainerImageByDisk = (vm: VMKind, name: string) => getVolumeContainerImage(getVolumes(vm).find((vol) => name === vol.name)); diff --git a/frontend/packages/kubevirt-plugin/src/selectors/vm/vmlike.ts b/frontend/packages/kubevirt-plugin/src/selectors/vm/vmlike.ts index 6e065c0b9b1..e82517a86cd 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/vm/vmlike.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/vm/vmlike.ts @@ -1,17 +1,25 @@ import { K8sKind } from '@console/internal/module/k8s'; import { TemplateModel } from '@console/internal/models'; -import { VMLikeEntityKind, VMKind, VMIKind } from '../../types'; -import { VirtualMachineModel } from '../../models'; -import { selectVM } from '../vm-template/selectors'; +import { VMKind, VMIKind } from '../../types/vm'; +import { VMGenericLikeEntityKind } from '../../types/vmLike'; +import { VirtualMachineModel, VirtualMachineInstanceModel } from '../../models'; +import { selectVM } from '../vm-template/basic'; -export const isVM = (vmLikeEntity: VMLikeEntityKind | VMIKind): vmLikeEntity is VMKind => +export const isVM = (vmLikeEntity: VMGenericLikeEntityKind): vmLikeEntity is VMKind => vmLikeEntity && vmLikeEntity.kind === VirtualMachineModel.kind; -export const getVMLikeModel = (vmLikeEntity: VMLikeEntityKind): K8sKind => - isVM(vmLikeEntity) ? VirtualMachineModel : TemplateModel; +export const isVMI = (vmLikeEntity: VMGenericLikeEntityKind): vmLikeEntity is VMIKind => + vmLikeEntity && vmLikeEntity.kind === VirtualMachineInstanceModel.kind; -export const asVM = (vmLikeEntity: VMLikeEntityKind): VMKind => { - if (!vmLikeEntity) { +export const getVMLikeModel = (vmLikeEntity: VMGenericLikeEntityKind): K8sKind => + isVM(vmLikeEntity) + ? VirtualMachineModel + : isVMI(vmLikeEntity) + ? VirtualMachineInstanceModel + : TemplateModel; + +export const asVM = (vmLikeEntity: VMGenericLikeEntityKind): VMKind => { + if (!vmLikeEntity || isVMI(vmLikeEntity)) { return null; } diff --git a/frontend/packages/kubevirt-plugin/src/selectors/vmi/basic.ts b/frontend/packages/kubevirt-plugin/src/selectors/vmi/basic.ts index 07dbb7ec138..31758e4c218 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/vmi/basic.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/vmi/basic.ts @@ -1,13 +1,24 @@ import * as _ from 'lodash'; -import { VMIKind } from '../../types'; +import { V1Network, V1NetworkInterface, VMIKind } from '../../types'; +import { V1Disk } from '../../types/vm/disk/V1Disk'; +import { V1Volume } from '../../types/vm/disk/V1Volume'; -export const getVMIDisks = (vmi: VMIKind): VMIKind['spec']['domain']['devices']['disks'] => +export const getVMIDisks = (vmi: VMIKind, defaultValue: V1Disk[] = []): V1Disk[] => vmi && vmi.spec && vmi.spec.domain && vmi.spec.domain.devices && vmi.spec.domain.devices.disks ? vmi.spec.domain.devices.disks - : []; + : defaultValue; -export const getVMINetworks = (vmi: VMIKind): VMIKind['spec']['networks'] => - vmi && vmi.spec && vmi.spec.networks ? vmi.spec.networks : []; +export const getVMINetworks = (vmi: VMIKind, defaultValue: V1Network[] = []): V1Network[] => + vmi && vmi.spec && vmi.spec.networks ? vmi.spec.networks : defaultValue; + +export const getVMIVolumes = (vmi: VMIKind, defaultValue: V1Volume[] = []): V1Volume[] => + vmi && vmi.spec && vmi.spec.volumes ? vmi.spec.volumes : defaultValue; + +export const getVMIInterfaces = ( + vmi: VMIKind, + defaultValue: V1NetworkInterface[] = [], +): V1NetworkInterface[] => + _.has(vmi, 'spec.domain.devices.interfaces') ? vmi.spec.domain.devices.interfaces : defaultValue; export const getVMIConditionsByType = ( vmi: VMIKind, @@ -22,7 +33,7 @@ export const getVmiTemplateLabels = (vmi: VMIKind): { [key: string]: string } => export const isVMIRunning = (vmi: VMIKind) => vmi && vmi.status && vmi.status.phase === 'Running'; -export const getVMIInterfaces = (vmi: VMIKind) => +export const getVMIAvailableStatusInterfaces = (vmi: VMIKind) => (vmi && vmi.status && vmi.status.interfaces) || []; export const getVMINodeName = (vmi: VMIKind) => vmi && vmi.status && vmi.status.nodeName; diff --git a/frontend/packages/kubevirt-plugin/src/statuses/vm/vm.ts b/frontend/packages/kubevirt-plugin/src/statuses/vm/vm.ts index 0c3750eb952..f5db613dd50 100644 --- a/frontend/packages/kubevirt-plugin/src/statuses/vm/vm.ts +++ b/frontend/packages/kubevirt-plugin/src/statuses/vm/vm.ts @@ -18,6 +18,7 @@ import { isVMCreated, findConversionPod, getVMStatusConditions, + isVMI, } from '../../selectors/vm'; import { getPodStatus } from '../pod/pod'; import { @@ -50,8 +51,10 @@ import { } from './constants'; import { Status } from '..'; import { isVMIPaused } from '../../selectors/vmi/basic'; +import { getPhase } from '@console/noobaa-storage-plugin/src/utils'; +import { VMILikeEntityKind } from '../../types/vmLike'; -const isBeingMigrated = (vm: VMKind, migrations?: K8sResourceKind[]): VMStatus => { +const isBeingMigrated = (vm: VMILikeEntityKind, migrations?: K8sResourceKind[]): VMStatus => { const migration = findVMIMigration(vm, migrations); if (isMigrating(migration)) { return { status: VM_STATUS_MIGRATING, message: getMigrationStatusPhase(migration) }; @@ -59,7 +62,11 @@ const isBeingMigrated = (vm: VMKind, migrations?: K8sResourceKind[]): VMStatus = return NOT_HANDLED; }; -const isBeingStopped = (vm: VMKind, launcherPod: PodKind = null): VMStatus => { +const isBeingStopped = (vm: VMILikeEntityKind, launcherPod: PodKind = null): VMStatus => { + if (isVMI(vm)) { + return NOT_HANDLED; + } + if (isVMReady(vm) || isVMCreated(vm)) { const podStatus = getPodStatus(launcherPod); const containerStatuses = getPodContainerStatuses(launcherPod); @@ -84,8 +91,7 @@ const isBeingStopped = (vm: VMKind, launcherPod: PodKind = null): VMStatus => { return NOT_HANDLED; }; -const isRunning = (vm: VMKind): VMStatus => - isVMRunning(vm) ? NOT_HANDLED : { status: VM_STATUS_OFF }; +const isOff = (vm: VMKind): VMStatus => (isVMRunning(vm) ? NOT_HANDLED : { status: VM_STATUS_OFF }); const isReady = (vmi: VMIKind, launcherPod: PodKind): VMStatus => { if ((getStatusPhase(vmi) || '').toLowerCase() === 'running') { @@ -101,7 +107,7 @@ const isReady = (vmi: VMIKind, launcherPod: PodKind): VMStatus => { const isPaused = (vmi: VMIKind): VMStatus => isVMIPaused(vmi) ? { status: VM_STATUS_PAUSED } : NOT_HANDLED; -const isVMError = (vm: VMKind): VMStatus => { +const isVMError = (vm: VMILikeEntityKind): VMStatus => { // is an issue with the VM definition? const condition = getVMStatusConditions(vm)[0]; if (condition) { @@ -113,7 +119,11 @@ const isVMError = (vm: VMKind): VMStatus => { return NOT_HANDLED; }; -const isCreated = (vm: VMKind, launcherPod: PodKind = null): VMStatus => { +const isCreated = (vm: VMILikeEntityKind, launcherPod: PodKind = null): VMStatus => { + if (isVMI(vm)) { + return NOT_HANDLED; + } + if (isVMCreated(vm)) { // created but not yet ready if (launcherPod) { @@ -182,7 +192,7 @@ const isBeingImported = (vm: VMKind, pods?: PodKind[]): VMStatus => { return NOT_HANDLED; }; -const isV2VConversion = (vm: VMKind, pods?: PodKind[]): VMStatus => { +const isV2VConversion = (vm: VMILikeEntityKind, pods?: PodKind[]): VMStatus => { const conversionPod = findConversionPod(vm, pods); const podPhase = getPodStatusPhase(conversionPod); if (conversionPod && podPhase !== POD_PHASE_SUCEEDED) { @@ -235,23 +245,28 @@ export const getVMStatus = ({ pods, migrations, }: { - vm: VMKind; + vm?: VMKind; vmi?: VMIKind; pods?: PodKind[]; migrations?: K8sResourceKind[]; }): VMStatus => { - const launcherPod = findVMPod(vm, pods); + const vmLike = vm || vmi; + const launcherPod = findVMPod(vmLike, pods); return ( isPaused(vmi) || - isV2VConversion(vm, pods) || // these statuses must precede isRunning() because they do not rely on ready vms - isBeingMigrated(vm, migrations) || // -||- - isBeingImported(vm, pods) || // -||- - isBeingStopped(vm, launcherPod) || - isRunning(vm) || + isV2VConversion(vmLike, pods) || // these statuses must precede isRunning() because they do not rely on ready vms + isBeingMigrated(vmLike, migrations) || // -||- + (vm && isBeingImported(vm, pods)) || // -||- + isBeingStopped(vmLike, launcherPod) || + (vm && isOff(vm)) || isReady(vmi, launcherPod) || - isVMError(vm) || - isCreated(vm, launcherPod) || - isWaitingForVMI(vm) || { status: VM_STATUS_UNKNOWN } + isVMError(vmLike) || + isCreated(vmLike, launcherPod) || + (!vmi && vm && isWaitingForVMI(vm)) || + (getPhase(vmi) === 'Running' && { status: VM_STATUS_RUNNING }) || + (['Scheduling', 'Scheduled'].includes(getPhase(vmi)) && { status: VM_STATUS_STARTING }) || + (getPhase(vmi) === 'Pending' && { status: VM_STATUS_VMI_WAITING }) || + (getPhase(vmi) === 'Failed' && { status: VM_STATUS_ERROR }) || { status: VM_STATUS_UNKNOWN } ); }; diff --git a/frontend/packages/kubevirt-plugin/src/types/types.ts b/frontend/packages/kubevirt-plugin/src/types/types.ts index a3f0f9f9189..948e7ca16b0 100644 --- a/frontend/packages/kubevirt-plugin/src/types/types.ts +++ b/frontend/packages/kubevirt-plugin/src/types/types.ts @@ -1,10 +1,8 @@ -import { PodKind, TemplateKind } from '@console/internal/module/k8s'; +import { PodKind } from '@console/internal/module/k8s'; import { DeviceType } from '../constants'; -import { VMKind, V1NetworkInterface } from './vm'; +import { V1NetworkInterface } from './vm'; import { V1Disk } from './vm/disk/V1Disk'; -export type VMLikeEntityKind = VMKind | TemplateKind; - export type VMMultiStatus = { status: string; message?: string; diff --git a/frontend/packages/kubevirt-plugin/src/types/vmLike.ts b/frontend/packages/kubevirt-plugin/src/types/vmLike.ts new file mode 100644 index 00000000000..568890f8ff4 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/types/vmLike.ts @@ -0,0 +1,6 @@ +import { VMIKind, VMKind } from './vm'; +import { TemplateKind } from '@console/internal/module/k8s'; + +export type VMILikeEntityKind = VMKind | VMIKind; +export type VMLikeEntityKind = VMKind | TemplateKind; +export type VMGenericLikeEntityKind = VMLikeEntityKind | VMILikeEntityKind; diff --git a/frontend/packages/kubevirt-plugin/src/utils/validations/vm/disk.ts b/frontend/packages/kubevirt-plugin/src/utils/validations/vm/disk.ts index 13c57188c10..b3a0da99c8e 100644 --- a/frontend/packages/kubevirt-plugin/src/utils/validations/vm/disk.ts +++ b/frontend/packages/kubevirt-plugin/src/utils/validations/vm/disk.ts @@ -16,6 +16,7 @@ import { CombinedDisk } from '../../../k8s/wrapper/vm/combined-disk'; import { PersistentVolumeClaimWrapper } from '../../../k8s/wrapper/vm/persistent-volume-claim-wrapper'; import { DiskBus } from '../../../constants/vm/storage/disk-bus'; import { DiskType } from '../../../constants/vm/storage/disk-type'; +import { UIDiskValidation } from './types'; const validateDiskName = (name: string, usedDiskNames: Set): ValidationObject => { let validation = validateDNS1123SubdomainValue(name); @@ -147,16 +148,3 @@ export const validateDisk = ( isValid: !!hasAllRequiredFilled && !Object.keys(validations).find((key) => validations[key]), }; }; - -export type UIDiskValidation = { - validations: { - name?: ValidationObject; - size?: ValidationObject; - url?: ValidationObject; - container?: ValidationObject; - diskInterface?: ValidationObject; - pvc?: ValidationObject; - }; - isValid: boolean; - hasAllRequiredFilled: boolean; -}; diff --git a/frontend/packages/kubevirt-plugin/src/utils/validations/vm/types.ts b/frontend/packages/kubevirt-plugin/src/utils/validations/vm/types.ts new file mode 100644 index 00000000000..2e30c24c6c2 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/utils/validations/vm/types.ts @@ -0,0 +1,14 @@ +import { ValidationObject } from '@console/shared/src'; + +export type UIDiskValidation = { + validations: { + name?: ValidationObject; + size?: ValidationObject; + url?: ValidationObject; + container?: ValidationObject; + diskInterface: ValidationObject; + pvc?: ValidationObject; + }; + isValid: boolean; + hasAllRequiredFilled: boolean; +};