diff --git a/libs/locales/lib/en/translation.json b/libs/locales/lib/en/translation.json index cd7107f4ea..d3a82aa284 100644 --- a/libs/locales/lib/en/translation.json +++ b/libs/locales/lib/en/translation.json @@ -660,6 +660,9 @@ "ai:Registering": "Registering", "ai:Release domain resolution": "Release domain resolution", "ai:Remove": "Remove", + "ai:Remove bond": "Remove bond", + "ai:Remove bond dialog": "Remove bond dialog", + "ai:Remove bond?": "Remove bond?", "ai:Remove from the cluster": "Remove from the cluster", "ai:Remove host": "Remove host", "ai:Remove host?": "Remove host?", @@ -749,6 +752,7 @@ "ai:Technology preview features provide early access to upcoming product innovations, enabling you to test functionality and provide feedback during the development process.": "Technology preview features provide early access to upcoming product innovations, enabling you to test functionality and provide feedback during the development process.", "ai:The agent is not bound to a cluster.": "The agent is not bound to a cluster.", "ai:The agent ran successfully": "The agent ran successfully", + "ai:The bond associated with the host will be removed.": "The bond associated with the host will be removed.", "ai:The classic bullet-proof networking type": "The classic bullet-proof networking type", "ai:The cluster can not be installed yet because there are no available hosts with {{cpuArchitecture}} architecture found. To continue:": "The cluster cannot be installed because there are no available hosts with {{cpuArchitecture}} architecture found. To continue:", "ai:The cluster has 0 hosts. No workloads will be able to run.": "The cluster has 0 hosts. No workloads will be able to run.", diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/CollapsedHost.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/CollapsedHost.tsx index 505341ee69..addef334cb 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/CollapsedHost.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/CollapsedHost.tsx @@ -17,6 +17,21 @@ export type HostSummaryProps = { numInterfaces: number; hostIdx: number; hasError: boolean; + bondPrimaryInterface: string; + bondSecondaryInterface: string; +}; + +const getLabelCollapsedHost = ( + macAddress: string, + mappingValue: string, + bondPrimaryInterface: string, + bondSecondaryInterface: string, +) => { + if (bondPrimaryInterface !== '' && bondSecondaryInterface !== '') { + return `${bondPrimaryInterface}/${bondSecondaryInterface} -> ${mappingValue}`; + } else { + return `${macAddress} -> ${mappingValue}`; + } }; const HostSummary: React.FC = ({ @@ -26,6 +41,8 @@ const HostSummary: React.FC = ({ numInterfaces, hasError, hostIdx, + bondPrimaryInterface, + bondSecondaryInterface, }) => { return ( <> @@ -49,10 +66,14 @@ const HostSummary: React.FC = ({ {!hasError && ( <> - {' '} + {' '} {numInterfaces > 1 && ( diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewHosts/BondsConfirmationModal.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewHosts/BondsConfirmationModal.tsx new file mode 100644 index 0000000000..7ee6e91542 --- /dev/null +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewHosts/BondsConfirmationModal.tsx @@ -0,0 +1,51 @@ +import * as React from 'react'; +import { + Button, + ButtonVariant, + Modal, + ModalBoxBody, + ModalBoxFooter, + Stack, + StackItem, +} from '@patternfly/react-core'; + +import { useTranslation } from '../../../../../../common/hooks/use-translation-wrapper'; + +type BondDeleteModalModalProps = { + isOpen: boolean; + onConfirm: VoidFunction; + onCancel: VoidFunction; +}; + +const BondDeleteModalModal = ({ isOpen, onConfirm, onCancel }: BondDeleteModalModalProps) => { + const { t } = useTranslation(); + + return ( + + + + {t('ai:The bond associated with the host will be removed.')} + + + + + + + + ); +}; + +export default BondDeleteModalModal; diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewHosts/BondsSelect.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewHosts/BondsSelect.tsx new file mode 100644 index 0000000000..47ffe84c8b --- /dev/null +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewHosts/BondsSelect.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { SelectFieldProps } from '../../../../../../common/components/ui/formik/types'; +import { SelectField } from '../../../../../../common'; + +type BondsSelectProps = { + onChange?: SelectFieldProps['onChange']; + name: string; +}; + +const bondsList = [ + { value: 'balance-rr', label: 'Balance-rr (0)', default: false }, + { value: 'active-backup', label: 'Active-Backup (1)', default: true }, + { value: 'balance-xor', label: 'Balance-xor (2)', default: false }, + { value: 'broadcast', label: 'Broadcast (3)', default: false }, + { value: '802.3ad', label: '802.3ad (4)', default: false }, + { value: 'balance-tlb', label: 'Balance-tlb (5)', default: false }, + { value: 'balance-alb', label: 'Balance-alb (6)', default: false }, +]; +const BondsSelect: React.FC = ({ onChange, name }) => { + const selectOptions = bondsList.map((version) => ({ + label: version.label, + value: version.value, + })); + return ( + + ); +}; + +export default BondsSelect; diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewHosts/FormViewHostsFields.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewHosts/FormViewHostsFields.tsx index 2c8c4c1d6e..d598228f90 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewHosts/FormViewHostsFields.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewHosts/FormViewHostsFields.tsx @@ -1,38 +1,114 @@ import React from 'react'; import { FormGroup, Grid, TextContent, Text, TextVariants } from '@patternfly/react-core'; -import { useField } from 'formik'; +import { useField, useFormikContext } from 'formik'; import StaticIpHostsArray, { HostComponentProps } from '../StaticIpHostsArray'; -import { getFieldId } from '../../../../../../common'; +import { getFieldId, PopoverIcon } from '../../../../../../common'; import HostSummary from '../CollapsedHost'; import { FormViewHost, StaticProtocolType } from '../../data/dataTypes'; import { getProtocolVersionLabel, getShownProtocolVersions } from '../../data/protocolVersion'; import { getEmptyFormViewHost } from '../../data/emptyData'; -import { OcmInputField } from '../../../../ui/OcmFormFields'; +import { OcmCheckboxField, OcmInputField } from '../../../../ui/OcmFormFields'; +import '../staticIp.css'; +import BondsSelect from './BondsSelect'; +import BondsConfirmationModal from './BondsConfirmationModal'; const getExpandedHostComponent = (protocolType: StaticProtocolType) => { const Component: React.FC = ({ fieldName, hostIdx }) => { + const [isModalOpen, setIsModalOpen] = React.useState(false); + const { setFieldValue } = useFormikContext(); + const [bondPrimaryField] = useField(`${fieldName}.bondPrimaryInterface`); + const [bondSecondaryField] = useField(`${fieldName}.bondSecondaryInterface`); + const [useBond] = useField(`${fieldName}.useBond`); + + const handleUseBondChange = (checked: boolean) => { + if (!checked) { + if (bondPrimaryField.value || bondSecondaryField.value) { + setIsModalOpen(true); + } else { + setFieldValue(`${fieldName}.useBond`, false); + setFieldValue(`${fieldName}.bondType`, 'active-backup'); + setFieldValue(`${fieldName}.bondPrimaryInterface`, ''); + setFieldValue(`${fieldName}.bondSecondaryInterface`, ''); + } + } + }; + + const handleModalConfirm = () => { + setFieldValue(`${fieldName}.useBond`, false); + setFieldValue(`${fieldName}.bondType`, 'active-backup'); + setFieldValue(`${fieldName}.bondPrimaryInterface`, ''); + setFieldValue(`${fieldName}.bondSecondaryInterface`, ''); + setIsModalOpen(false); + }; + + const handleModalCancel = () => { + setIsModalOpen(false); + }; return ( - - - {getShownProtocolVersions(protocolType).map((protocolVersion) => ( - + <> + + + + {'Use bond'}{' '} + + + } + onChange={(value) => handleUseBondChange(value)} + name={`${fieldName}.useBond`} + /> + + {useBond.value && ( + + + + + {' '} + + + )} + {!useBond.value && ( {' '} - - ))} - + data-testid={`mac-address-${hostIdx}`} + /> + )} + {getShownProtocolVersions(protocolType).map((protocolVersion) => ( + + {' '} + + ))} + + + ); }; return Component; @@ -47,14 +123,17 @@ const getCollapsedHostComponent = (protocolType: StaticProtocolType) => { (protocolVersion) => value.ips[protocolVersion], ); const mapValue = ipAddresses.join(', '); + return ( ); }; diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewHosts/formViewHostsValidationSchema.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewHosts/formViewHostsValidationSchema.tsx index 085863f3cc..94536aeb0a 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewHosts/formViewHostsValidationSchema.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/FormViewHosts/formViewHostsValidationSchema.tsx @@ -26,13 +26,29 @@ const getAllIpv6Addresses: UniqueStringArrayExtractor = ( const getAllMacAddresses: UniqueStringArrayExtractor = ( values: FormViewHostsValues, -) => values.hosts.map((host) => host.macAddress); +) => { + return values.hosts.map((host) => host.macAddress); +}; + +const getAllBondInterfaces: UniqueStringArrayExtractor = ( + values: FormViewHostsValues, +) => { + return values.hosts.flatMap((host) => [ + host.bondPrimaryInterface.toLowerCase(), + host.bondSecondaryInterface.toLowerCase(), + ]); +}; const getHostValidationSchema = (networkWideValues: FormViewNetworkWideValues) => Yup.object({ - macAddress: macAddressValidationSchema - .required(requiredMsg) - .concat(getUniqueValidationSchema(getAllMacAddresses)), + macAddress: Yup.mixed().when('useBond', { + is: false, + then: () => + macAddressValidationSchema + .required(requiredMsg) + .concat(getUniqueValidationSchema(getAllMacAddresses)), + otherwise: () => Yup.mixed().notRequired(), + }), ips: Yup.object({ ipv4: showIpv4(networkWideValues.protocolType) ? getInMachineNetworkValidationSchema( @@ -63,6 +79,18 @@ const getHostValidationSchema = (networkWideValues: FormViewNetworkWideValues) = ) : Yup.string(), }), + bondPrimaryInterface: Yup.mixed().when('useBond', { + is: true, + then: () => + macAddressValidationSchema.concat(getUniqueValidationSchema(getAllBondInterfaces)), + otherwise: () => Yup.mixed().notRequired(), + }), + bondSecondaryInterface: Yup.mixed().when('useBond', { + is: true, + then: () => + macAddressValidationSchema.concat(getUniqueValidationSchema(getAllBondInterfaces)), + otherwise: () => Yup.mixed().notRequired(), + }), }); export const getFormViewHostsValidationSchema = (networkWideValues: FormViewNetworkWideValues) => { diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/YamlView/YamlViewFields.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/YamlView/YamlViewFields.tsx index a9303427b6..ce9d85cd18 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/YamlView/YamlViewFields.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/components/YamlView/YamlViewFields.tsx @@ -34,6 +34,8 @@ const CollapsedHost: React.FC = ({ fieldName, hostIdx }) => mappingValue={mapValue} hostIdx={hostIdx} hasError={hasError} + bondPrimaryInterface="" + bondSecondaryInterface="" /> ); }; diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/dataTypes.ts b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/dataTypes.ts index dc3672e803..dfd18a01a1 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/dataTypes.ts +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/dataTypes.ts @@ -31,6 +31,10 @@ export type MachineNetworks = { [protocolVersion in ProtocolVersion]: string }; export type FormViewHost = { macAddress: string; ips: HostIps; + useBond: boolean; + bondType: string; + bondPrimaryInterface: string; + bondSecondaryInterface: string; }; export type StaticFormData = { diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/emptyData.ts b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/emptyData.ts index 0548a7cf9c..aa089c8825 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/emptyData.ts +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/emptyData.ts @@ -19,6 +19,10 @@ export const getEmptyFormViewHost = (): FormViewHost => { return { macAddress: '', ips: getEmptyHostIps(), + useBond: false, + bondType: 'active-backup', + bondPrimaryInterface: '', + bondSecondaryInterface: '', }; }; diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/formDataFromInfraEnvField.ts b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/formDataFromInfraEnvField.ts index 6fd1543450..56b0da551e 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/formDataFromInfraEnvField.ts +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/formDataFromInfraEnvField.ts @@ -24,6 +24,8 @@ import { isVlanInterface, NmstateEthernetInterface, NmstateVlanInterface, + NmstateBondInterface, + NmstateInterfaceType, } from './nmstateTypes'; import { isDummyInterface } from './dummyData'; @@ -64,7 +66,7 @@ const getVlanId = (interfaces: NmstateInterface[]): number | null => { }; const getIpAddress = ( - networkInterface: NmstateEthernetInterface | NmstateVlanInterface, + networkInterface: NmstateEthernetInterface | NmstateVlanInterface | NmstateBondInterface, protocolVersion: ProtocolVersion, ): string => { const ipAddressData = networkInterface[protocolVersion]; @@ -145,8 +147,19 @@ const getFormViewHost = ( ipv4: '', ipv6: '', }, + bondType: 'active-backup', + bondPrimaryInterface: '', + bondSecondaryInterface: '', + useBond: false, }; + if (realInterface.type === NmstateInterfaceType.BOND) { + ret.useBond = true; + ret.bondType = realInterface['link-aggregation'].mode; + ret.bondPrimaryInterface = infraEnvHost.macInterfaceMap[0].macAddress ?? ''; + ret.bondSecondaryInterface = infraEnvHost.macInterfaceMap[1].macAddress ?? ''; + } + for (const protocolVersion of getShownProtocolVersions(protocolType)) { ret.ips[protocolVersion] = getIpAddress(realInterface, protocolVersion); } diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/formDataToInfraEnvField.ts b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/formDataToInfraEnvField.ts index d44a15c63b..f4ac00f4b2 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/formDataToInfraEnvField.ts +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/formDataToInfraEnvField.ts @@ -2,6 +2,7 @@ import { dump } from 'js-yaml'; import { HostStaticNetworkConfig, InfraEnv, + MacInterfaceMap, } from '@openshift-assisted/types/assisted-installer-service'; import { ProtocolVersion, @@ -34,6 +35,8 @@ import { getMachineNetworkCidr } from './machineNetwork'; import { getEmptyHostIps } from './emptyData'; const REAL_NIC_NAME = 'eth0'; +const REAL_NIC_NAME_1 = 'eth1'; +const BOND_NIC_NAME = 'bond0'; const getPrefixLength = ( networkWide: FormViewNetworkWideValues, @@ -54,6 +57,8 @@ const toYamlWithComments = (json: object, comments: string[]) => { const getNmstateObject = ( networkWide: FormViewNetworkWideValues, hostIps: Partial, + bondType?: string, + hasBondsConfigured?: boolean, ): Nmstate => { const interfaces: NmstateInterface[] = []; const routeConfigs: NmstateRoutesConfig[] = []; @@ -63,9 +68,8 @@ const getNmstateObject = ( for (const protocolVersion of getShownProtocolVersions(networkWide.protocolType)) { const hostIp = hostIps[protocolVersion]; let nicName = ''; - if (hostIp) { - nicName = REAL_NIC_NAME; + nicName = bondType && hasBondsConfigured ? BOND_NIC_NAME : REAL_NIC_NAME; realProtocolConfigs[protocolVersion] = getNmstateProtocolConfig( hostIp, getPrefixLength(networkWide, protocolVersion), @@ -79,7 +83,9 @@ const getNmstateObject = ( ), }; nicName = getDummyNicName(protocolVersion); - interfaces.push(getInterface(nicName, protocolConfigs, networkWide)); + interfaces.push( + getInterface(nicName, protocolConfigs, networkWide, bondType, hasBondsConfigured), + ); } routeConfigs.push( @@ -94,7 +100,15 @@ const getNmstateObject = ( } if (Object.keys(realProtocolConfigs).length > 0) { - interfaces.unshift(getInterface(REAL_NIC_NAME, realProtocolConfigs, networkWide)); + interfaces.unshift( + getInterface( + bondType && hasBondsConfigured ? BOND_NIC_NAME : REAL_NIC_NAME, + realProtocolConfigs, + networkWide, + bondType, + hasBondsConfigured, + ), + ); } const nmstate = { @@ -119,7 +133,12 @@ const toYaml = ( ); } const hostIps = formHostData ? formHostData.ips : getEmptyHostIps(); - const nmstate = getNmstateObject(networkWideConfiguration, hostIps); + const bondType = formHostData ? formHostData.bondType : ''; + const hasBondsConfigured = + formHostData && + formHostData.bondPrimaryInterface !== '' && + formHostData.bondSecondaryInterface !== ''; + const nmstate = getNmstateObject(networkWideConfiguration, hostIps, bondType, hasBondsConfigured); return toYamlWithComments(nmstate, comments); }; @@ -137,18 +156,40 @@ const formDataToInfraEnvField = (formData: StaticFormData): HostStaticNetworkCon for (const host of formData.hosts) { ret.push({ networkYaml: toYaml(formData.networkWide, host), - macInterfaceMap: [ - { - macAddress: host.macAddress, - logicalNicName: REAL_NIC_NAME, - }, - ], + macInterfaceMap: getMapInterfaceMap(host), }); } } return ret; }; +export const getMapInterfaceMap = (host: FormViewHost): MacInterfaceMap => { + if ( + host.bondPrimaryInterface && + host.bondPrimaryInterface !== '' && + host.bondSecondaryInterface && + host.bondSecondaryInterface !== '' + ) { + return [ + { + macAddress: host.bondPrimaryInterface, + logicalNicName: REAL_NIC_NAME, + }, + { + macAddress: host.bondSecondaryInterface, + logicalNicName: REAL_NIC_NAME_1, + }, + ]; + } else { + return [ + { + macAddress: host.macAddress, + logicalNicName: REAL_NIC_NAME, + }, + ]; + } +}; + export const formViewHostsToInfraEnvField = ( currentInfraEnv: InfraEnv, formViewHosts: FormViewHost[], diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/nmstateTypes.ts b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/nmstateTypes.ts index 4e0c32d5a8..c4269b54dc 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/nmstateTypes.ts +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/nmstateTypes.ts @@ -3,6 +3,7 @@ import { ProtocolVersion } from './dataTypes'; export enum NmstateInterfaceType { ETHERNET = 'ethernet', VLAN = 'vlan', + BOND = 'bond', } export type NmstateAddress = { @@ -53,16 +54,19 @@ export type NmstateVlanInterface = { }; } & NmstateProtocolConfigs; -export type NmstateInterface = NmstateEthernetInterface | NmstateVlanInterface; +export type NmstateInterface = + | NmstateEthernetInterface + | NmstateVlanInterface + | NmstateBondInterface; export const isVlanInterface = ( - nmStateInterface: NmstateVlanInterface | NmstateEthernetInterface, + nmStateInterface: NmstateVlanInterface | NmstateEthernetInterface | NmstateBondInterface, ): nmStateInterface is NmstateVlanInterface => { return nmStateInterface.type === NmstateInterfaceType.VLAN; }; export const isEthernetInterface = ( - nmStateInterface: NmstateVlanInterface | NmstateEthernetInterface, + nmStateInterface: NmstateVlanInterface | NmstateEthernetInterface | NmstateBondInterface, ): nmStateInterface is NmstateEthernetInterface => { return nmStateInterface.type === NmstateInterfaceType.ETHERNET; }; @@ -79,3 +83,23 @@ export type Nmstate = { }; interfaces: NmstateInterface[]; }; + +export type NmstateBondInterface = { + name: string; + type: NmstateInterfaceType.BOND; + state: string; +} & NmstateProtocolConfigs & { + 'link-aggregation': { + mode: string; + options: { + miimon: string; + }; + port: string[]; + }; + }; + +export const isBondInterface = ( + nmStateInterface: NmstateVlanInterface | NmstateEthernetInterface | NmstateBondInterface, +): nmStateInterface is NmstateEthernetInterface => { + return nmStateInterface.type === NmstateInterfaceType.BOND; +}; diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/nmstateYaml.ts b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/nmstateYaml.ts index 596b0c3bb4..68c725a8ee 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/nmstateYaml.ts +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/staticIp/data/nmstateYaml.ts @@ -16,6 +16,9 @@ import { } from './dataTypes'; import findLastIndex from 'lodash-es/findLastIndex.js'; +const REAL_NIC_NAME = 'eth0'; +const REAL_NIC_NAME_1 = 'eth1'; + const ROUTE_DESTINATIONS = { ipv4: '0.0.0.0/0', ipv6: '::/0', @@ -99,6 +102,8 @@ export const getInterface = ( nicName: string, protocolConfigs: NmstateProtocolConfigs, networkWide: FormViewNetworkWideValues, + bondType?: string, + hasBondsConfigured?: boolean, ): NmstateInterface => { if (networkWide.useVlan && networkWide.vlanId) { return { @@ -108,6 +113,20 @@ export const getInterface = ( vlan: { 'base-iface': nicName, id: networkWide.vlanId }, ...protocolConfigs, }; + } else if (bondType && hasBondsConfigured) { + return { + name: nicName, + type: NmstateInterfaceType.BOND, + state: 'up', + ...protocolConfigs, + 'link-aggregation': { + mode: bondType, + options: { + miimon: '100', + }, + port: [REAL_NIC_NAME, REAL_NIC_NAME_1], + }, + }; } else { return { name: nicName,