From c9cc9a29a8e20d160c3e21b5a1afe31100a5e48a Mon Sep 17 00:00:00 2001 From: Sergio Betanzos Date: Thu, 4 Nov 2021 16:44:54 +0100 Subject: [PATCH] F #5422: Add pci device section to template create form (#1560) --- .../booting/kernelSchema.js | 2 +- .../booting/ramdiskSchema.js | 2 +- .../ExtraConfiguration/inputOutput/index.js | 9 +- .../inputOutput/pciDevicesSchema.js | 92 +++++++++++++ .../inputOutput/pciDevicesSection.js | 122 ++++++++++++++++++ .../ExtraConfiguration/inputOutput/schema.js | 5 +- .../CreateForm/Steps/General/vmGroupSchema.js | 2 +- .../Steps/BasicConfiguration/vmGroupSchema.js | 2 +- src/fireedge/src/client/constants/host.js | 16 +++ .../src/client/constants/translates.js | 5 + src/fireedge/src/client/models/Host.js | 11 +- 11 files changed, 260 insertions(+), 8 deletions(-) create mode 100644 src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/pciDevicesSchema.js create mode 100644 src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/pciDevicesSection.js diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/kernelSchema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/kernelSchema.js index 833db6fcb4..7c779b5b9b 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/kernelSchema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/kernelSchema.js @@ -50,7 +50,7 @@ export const KERNEL_DS = { ?.sort((a, b) => { const compareOptions = { numeric: true, ignorePunctuation: true } - return a.value.localeCompare(b.value, undefined, compareOptions) + return a.text.localeCompare(b.text, undefined, compareOptions) }) }, validation: kernelValidation.when( diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/ramdiskSchema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/ramdiskSchema.js index 51d3a9564d..c0bd639df6 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/ramdiskSchema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/ramdiskSchema.js @@ -50,7 +50,7 @@ export const RAMDISK_DS = { ?.sort((a, b) => { const compareOptions = { numeric: true, ignorePunctuation: true } - return a.value.localeCompare(b.value, undefined, compareOptions) + return a.text.localeCompare(b.text, undefined, compareOptions) }) }, validation: ramdiskValidation.when( diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/index.js index 617b199fa8..1b8a28d6fc 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/index.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/index.js @@ -22,13 +22,15 @@ import { FormWithSchema } from 'client/components/Forms' import { STEP_ID as EXTRA_ID, TabType } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration' import InputsSection, { SECTION_ID as INPUT_ID } from './inputsSection' -import { INPUT_OUTPUT_FIELDS, INPUTS_FIELDS } from './schema' +import PciDevicesSection, { SECTION_ID as PCI_ID } from './pciDevicesSection' +import { INPUT_OUTPUT_FIELDS, INPUTS_FIELDS, PCI_FIELDS } from './schema' import { T } from 'client/constants' -export const TAB_ID = ['GRAPHICS', INPUT_ID] +export const TAB_ID = ['GRAPHICS', INPUT_ID, PCI_ID] const InputOutput = ({ hypervisor }) => { const inputsFields = useMemo(() => INPUTS_FIELDS(hypervisor), [hypervisor]) + const pciDevicesFields = useMemo(() => PCI_FIELDS(hypervisor), [hypervisor]) return ( { {inputsFields.length > 0 && ( )} + {pciDevicesFields.length > 0 && ( + + )} ) } diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/pciDevicesSchema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/pciDevicesSchema.js new file mode 100644 index 0000000000..301e53de14 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/pciDevicesSchema.js @@ -0,0 +1,92 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +import { string, array, object, ObjectSchema, ArraySchema } from 'yup' + +import { useHost } from 'client/features/One' +import { getPciDevices } from 'client/models/Host' +import { Field, arrayToOptions, filterFieldsByHypervisor, getValidationFromFields } from 'client/utils' +import { T, INPUT_TYPES, HYPERVISORS } from 'client/constants' + +const { vcenter, lxc, firecracker } = HYPERVISORS + +const transformPciToString = (pciDevice = {}) => { + const { DEVICE = '', VENDOR = '', CLASS = '' } = pciDevice + return [DEVICE, VENDOR, CLASS].join(',') +} + +const getPciAttributes = (pciDevice = '') => { + const [DEVICE, VENDOR, CLASS] = pciDevice.split(',') + return { DEVICE, VENDOR, CLASS } +} + +/** @type {Field} Name PCI device field */ +const DEVICE_NAME = { + name: 'DEVICE_NAME', + label: T.DeviceName, + notOnHypervisors: [vcenter, lxc, firecracker], + type: INPUT_TYPES.SELECT, + values: () => { + const hosts = useHost() + const pciDevices = hosts.map(getPciDevices).flat() + + return arrayToOptions(pciDevices, { + getText: ({ DEVICE_NAME } = {}) => DEVICE_NAME, + getValue: transformPciToString + }) + }, + validation: string().trim().notRequired(), + grid: { sm: 12, md: 3 } +} + +/** @type {Field} Common field properties */ +const commonFieldProps = name => ({ + name, + notOnHypervisors: [vcenter, lxc, firecracker], + type: INPUT_TYPES.TEXT, + dependOf: DEVICE_NAME.name, + watcher: pciDevice => { + if (pciDevice) { + const { [name]: attribute } = getPciAttributes(pciDevice) + + return attribute + } + }, + validation: string().trim().required(), + fieldProps: { disabled: true }, + grid: { xs: 12, sm: 4, md: 3 } +}) + +/** @type {Field} PCI device field */ +const DEVICE = { label: T.Device, ...commonFieldProps('DEVICE') } + +/** @type {Field} PCI device field */ +const VENDOR = { label: T.Vendor, ...commonFieldProps('VENDOR') } + +/** @type {Field} PCI device field */ +const CLASS = { label: T.Class, ...commonFieldProps('CLASS') } + +/** + * @param {string} [hypervisor] - VM hypervisor + * @returns {Field[]} List of Graphic inputs fields + */ +export const PCI_FIELDS = (hypervisor) => + filterFieldsByHypervisor([DEVICE_NAME, DEVICE, VENDOR, CLASS], hypervisor) + +/** @type {ObjectSchema} PCI devices object schema */ +export const PCI_SCHEMA = object(getValidationFromFields([DEVICE, VENDOR, CLASS])) + +/** @type {ArraySchema} PCI devices schema */ +export const PCI_DEVICES_SCHEMA = array(PCI_SCHEMA).ensure() diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/pciDevicesSection.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/pciDevicesSection.js new file mode 100644 index 0000000000..f4d339dc82 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/pciDevicesSection.js @@ -0,0 +1,122 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +import { JSXElementConstructor, useMemo } from 'react' +import PropTypes from 'prop-types' +import { Stack, FormControl, Divider, Button, IconButton } from '@mui/material' +import List from '@mui/material/List' +import ListItem from '@mui/material/ListItem' +import ListItemText from '@mui/material/ListItemText' +import { DeleteCircledOutline, AddCircledOutline } from 'iconoir-react' +import { useFieldArray, useForm, FormProvider } from 'react-hook-form' +import { yupResolver } from '@hookform/resolvers/yup' + +import { useHost } from 'client/features/One' +import { FormWithSchema, Legend } from 'client/components/Forms' +import { Translate } from 'client/components/HOC' +import { getPciDevices } from 'client/models/Host' + +import { STEP_ID as EXTRA_ID } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration' +import { PCI_SCHEMA } from './schema' +import { T } from 'client/constants' + +export const SECTION_ID = 'PCI' + +/** + * @param {object} props - Props + * @param {Array} props.fields - Fields + * @returns {JSXElementConstructor} - Inputs section + */ +const PciDevicesSection = ({ fields }) => { + const hosts = useHost() + const pciDevicesAvailable = useMemo(() => hosts.map(getPciDevices).flat(), [hosts.length]) + + const { fields: pciDevices, append, remove } = useFieldArray({ + name: `${EXTRA_ID}.${SECTION_ID}` + }) + + const methods = useForm({ + defaultValues: PCI_SCHEMA.default(), + resolver: yupResolver(PCI_SCHEMA) + }) + + const onSubmit = newInput => { + append(newInput) + methods.reset() + } + + return ( + + + + + + + + + + + {pciDevices?.map(({ id, DEVICE, VENDOR, CLASS }, index) => { + const deviceName = pciDevicesAvailable + .find(pciDevice => pciDevice?.DEVICE === DEVICE)?.DEVICE_NAME + + return ( + remove(index)}> + + + } + sx={{ '&:hover': { bgcolor: 'action.hover' } }} + > + + + ) + })} + + + ) +} + +PciDevicesSection.propTypes = { + fields: PropTypes.array +} + +PciDevicesSection.displayName = 'PciDevicesSection' + +export default PciDevicesSection diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/schema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/schema.js index 3f86c41791..0098e85c09 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/schema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/schema.js @@ -17,6 +17,7 @@ import { object, ObjectSchema } from 'yup' import { GRAPHICS_FIELDS } from './graphicsSchema' import { INPUTS_SCHEMA } from './inputsSchema' +import { PCI_DEVICES_SCHEMA } from './pciDevicesSchema' import { Field, getObjectSchemaFromFields } from 'client/utils' /** @@ -31,10 +32,12 @@ export const INPUT_OUTPUT_FIELDS = hypervisor => * @returns {ObjectSchema} I/O schema */ export const SCHEMA = hypervisor => object({ - INPUT: INPUTS_SCHEMA + INPUT: INPUTS_SCHEMA, + PCI: PCI_DEVICES_SCHEMA }).concat(getObjectSchemaFromFields([ ...GRAPHICS_FIELDS(hypervisor) ])) export * from './graphicsSchema' export * from './inputsSchema' +export * from './pciDevicesSchema' diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/vmGroupSchema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/vmGroupSchema.js index 6aaddd4fd5..5cffd44051 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/vmGroupSchema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/vmGroupSchema.js @@ -28,7 +28,7 @@ export const VM_GROUP_FIELD = { const vmGroups = useVmGroup() return vmGroups - ?.map(({ ID, NAME }) => ({ text: `#${ID} ${NAME}`, value: ID })) + ?.map(({ ID, NAME }) => ({ text: `#${ID} ${NAME}`, value: String(ID) })) ?.sort((a, b) => { const compareOptions = { numeric: true, ignorePunctuation: true } diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/vmGroupSchema.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/vmGroupSchema.js index 8f59e3811e..0e8fb361ea 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/vmGroupSchema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/vmGroupSchema.js @@ -27,7 +27,7 @@ export const VM_GROUP_FIELD = { const vmGroups = useVmGroup() return vmGroups - ?.map(({ ID, NAME }) => ({ text: `#${ID} ${NAME}`, value: ID })) + ?.map(({ ID, NAME }) => ({ text: `#${ID} ${NAME}`, value: String(ID) })) ?.sort((a, b) => { const compareOptions = { numeric: true, ignorePunctuation: true } diff --git a/src/fireedge/src/client/constants/host.js b/src/fireedge/src/client/constants/host.js index 308b1f8d7c..a177a0b0b6 100644 --- a/src/fireedge/src/client/constants/host.js +++ b/src/fireedge/src/client/constants/host.js @@ -16,6 +16,22 @@ import * as STATES from 'client/constants/states' import COLOR from 'client/constants/color' +/** + * @typedef {object} PciDevice - PCI device + * @property {string} DOMAIN - PCI address domain + * @property {string} BUS - PCI address bus + * @property {string} SLOT - PCI address slot + * @property {string} FUNCTION - PCI address function + * @property {string} ADDRESS - PCI address, bus, slot and function + * @property {string} DEVICE - Id of PCI device + * @property {string} CLASS - Id of PCI device class + * @property {string} VENDOR - Id of PCI device vendor + * @property {string} VMID - Id using this device, -1 if free + * @property {string} [DEVICE_NAME] - Name of PCI device + * @property {string} [VENDOR_NAME] - Name of PCI device vendor + * @property {string} [CLASS_NAME] - Name of PCI device class + */ + /** @type {STATES.StateInfo[]} Host states */ export const HOST_STATES = [ { diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js index 81e148ee11..1a262e94b5 100644 --- a/src/fireedge/src/client/constants/translates.js +++ b/src/fireedge/src/client/constants/translates.js @@ -473,6 +473,11 @@ module.exports = { /* VM Template schema - Input/Output */ InputOrOutput: 'Input / Output', Inputs: 'Inputs', + PciDevices: 'PCI Devices', + DeviceName: 'Device name', + Device: 'Device', + Vendor: 'Vendor', + Class: 'Class', /* VM Template schema - Input/Output - graphics */ Graphics: 'Graphics', VMRC: 'VMRC', diff --git a/src/fireedge/src/client/models/Host.js b/src/fireedge/src/client/models/Host.js index a155f90dca..6c12e4ef5c 100644 --- a/src/fireedge/src/client/models/Host.js +++ b/src/fireedge/src/client/models/Host.js @@ -14,7 +14,7 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ import { prettyBytes } from 'client/utils' -import { DEFAULT_CPU_MODELS, HOST_STATES, HYPERVISORS, StateInfo } from 'client/constants' +import { DEFAULT_CPU_MODELS, HOST_STATES, HYPERVISORS, PciDevice, StateInfo } from 'client/constants' /** * Returns information about the host state. @@ -79,6 +79,15 @@ export const getHugepageSizes = host => { .flat() } +/** + * Returns list of PCI devices from the host. + * + * @param {object} host - Host + * @returns {PciDevice[]} List of PCI devices from resource + */ +export const getPciDevices = host => + [host?.HOST_SHARE?.PCI_DEVICES?.PCI ?? []].flat().filter(Boolean) + /** * Returns list of KVM CPU Models available from the host pool. *