1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-21 14:50:08 +03:00

F #5422: Add pci device section to template create form (#1560)

This commit is contained in:
Sergio Betanzos 2021-11-04 16:44:54 +01:00 committed by GitHub
parent 51af5a24a0
commit c9cc9a29a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 260 additions and 8 deletions

View File

@ -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(

View File

@ -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(

View File

@ -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 (
<Stack
@ -45,6 +47,9 @@ const InputOutput = ({ hypervisor }) => {
{inputsFields.length > 0 && (
<InputsSection fields={inputsFields} />
)}
{pciDevicesFields.length > 0 && (
<PciDevicesSection fields={pciDevicesFields} />
)}
</Stack>
)
}

View File

@ -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()

View File

@ -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 (
<FormControl component='fieldset' sx={{ width: '100%', gridColumn: '1 / -1' }}>
<Legend title={T.PciDevices} />
<FormProvider {...methods}>
<Stack
direction='row' alignItems='flex-start' gap='0.5rem'
component='form'
onSubmit={methods.handleSubmit(onSubmit)}
>
<FormWithSchema
cy={`create-vm-template-${EXTRA_ID}.io-pci-devices`}
fields={fields}
rootProps={{ sx: { m: 0 } }}
/>
<Button
variant='outlined'
type='submit'
startIcon={<AddCircledOutline />}
sx={{ mt: '1em' }}
>
<Translate word={T.Add} />
</Button>
</Stack>
</FormProvider>
<Divider />
<List>
{pciDevices?.map(({ id, DEVICE, VENDOR, CLASS }, index) => {
const deviceName = pciDevicesAvailable
.find(pciDevice => pciDevice?.DEVICE === DEVICE)?.DEVICE_NAME
return (
<ListItem
key={id}
secondaryAction={
<IconButton onClick={() => remove(index)}>
<DeleteCircledOutline />
</IconButton>
}
sx={{ '&:hover': { bgcolor: 'action.hover' } }}
>
<ListItemText
primary={deviceName}
primaryTypographyProps={{ variant: 'body1' }}
secondary={[
`#${DEVICE}`,
`Vendor: ${VENDOR}`,
`Class: ${CLASS}`].join(' | ')}
/>
</ListItem>
)
})}
</List>
</FormControl>
)
}
PciDevicesSection.propTypes = {
fields: PropTypes.array
}
PciDevicesSection.displayName = 'PciDevicesSection'
export default PciDevicesSection

View File

@ -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'

View File

@ -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 }

View File

@ -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 }

View File

@ -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 = [
{

View File

@ -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',

View File

@ -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.
*