From edfd3b27493cc34bc87c42fbd894db491f5ebcb1 Mon Sep 17 00:00:00 2001 From: Matan Schatzman Date: Tue, 16 Jul 2024 09:31:08 +0300 Subject: [PATCH] CNV-38365: OVN bridge mapping Signed-off-by: Matan Schatzman --- package.json | 11 +-- scripts/start-console.sh | 6 +- .../components/PolicyForm/PolicyForm.tsx | 5 +- .../PolicyForm/PolicyFormOVSBridgeMapping.tsx | 54 +++++++++++++ .../PolicyFormOVSBridgeMappingExpandable.tsx | 76 +++++++++++++++++++ .../components/PolicyForm/PolicyInterface.tsx | 34 +++++++-- .../PolicyForm/PolicyInterfaceExpandable.tsx | 5 +- src/utils/components/PolicyForm/constants.ts | 3 +- src/utils/components/PolicyForm/utils.ts | 11 ++- yarn.lock | 47 ++++++------ 10 files changed, 213 insertions(+), 39 deletions(-) create mode 100644 src/utils/components/PolicyForm/PolicyFormOVSBridgeMapping.tsx create mode 100644 src/utils/components/PolicyForm/PolicyFormOVSBridgeMappingExpandable.tsx diff --git a/package.json b/package.json index 10a6e426..dab3e42f 100644 --- a/package.json +++ b/package.json @@ -26,11 +26,12 @@ "helm:build": "mkdir -p tmp && helm package -d tmp deployment/nmstate-console-plugin/" }, "devDependencies": { - "@kubevirt-ui/kubevirt-api": "^0.0.47", - "@openshift-console/dynamic-plugin-sdk": "1.1.0", - "@openshift-console/dynamic-plugin-sdk-webpack": "1.0.1", - "@openshift/dynamic-plugin-sdk": "~1.0.0", - "@openshift/dynamic-plugin-sdk-webpack": "~1.0.0", + "@kubevirt-ui/kubevirt-api": "^1.2.2", + "@openshift-console/dynamic-plugin-sdk": "1.4.0", + "@openshift-console/dynamic-plugin-sdk-webpack": "1.1.1", + "@openshift/dynamic-plugin-sdk": "~5.0.1", + "@openshift/dynamic-plugin-sdk-webpack": "~4.1.0", + "@patternfly/react-icons": "^5.1.1", "@patternfly/react-core": "5.1.1", "@patternfly/react-table": "^5.1.1", "@testing-library/jest-dom": "^5.16.5", diff --git a/scripts/start-console.sh b/scripts/start-console.sh index a22a7a46..c17eaf5a 100755 --- a/scripts/start-console.sh +++ b/scripts/start-console.sh @@ -38,5 +38,9 @@ if [ -x "$(command -v podman)" ]; then fi else BRIDGE_PLUGINS="${npm_package_consolePlugin_name}=http://host.docker.internal:9001" - docker run --pull=always --rm -p "$CONSOLE_PORT":9000 --env-file <(set | grep BRIDGE) $CONSOLE_IMAGE + if [ "$(uname)" == "Darwin" ]; then + docker run --platform=linux/x86_64 --pull=always --rm -p "$CONSOLE_PORT":9000 --env-file <(set | grep BRIDGE) $CONSOLE_IMAGE + else + docker run --pull=always --rm -p "$CONSOLE_PORT":9000 --env-file <(set | grep BRIDGE) $CONSOLE_IMAGE + fi fi diff --git a/src/utils/components/PolicyForm/PolicyForm.tsx b/src/utils/components/PolicyForm/PolicyForm.tsx index 6eb114bd..a161a8ce 100644 --- a/src/utils/components/PolicyForm/PolicyForm.tsx +++ b/src/utils/components/PolicyForm/PolicyForm.tsx @@ -14,7 +14,9 @@ import { import NodeSelectorModal from '../NodeSelectorModal/NodeSelectorModal'; import ApplySelectorCheckbox from './ApplySelectorCheckbox'; +import PolicyFormOVSBridgeMapping from './PolicyFormOVSBridgeMapping'; import PolicyInterfacesExpandable from './PolicyInterfaceExpandable'; +import { isOVSBridgeExisting } from './utils'; import './policy-form.scss'; @@ -28,6 +30,7 @@ type PolicyFormProps = { const PolicyForm: FC = ({ policy, setPolicy, createForm = false, formId }) => { const { t } = useNMStateTranslation(); const [modalOpen, setModalOpen] = useState(false); + const isOVSBridge = isOVSBridgeExisting(policy); const onDescriptionChange = (newDescription: string) => { setPolicy(({ metadata }) => { @@ -137,9 +140,9 @@ const PolicyForm: FC = ({ policy, setPolicy, createForm = false {t('Add another interface to the policy')} - + {isOVSBridge && } ); }; diff --git a/src/utils/components/PolicyForm/PolicyFormOVSBridgeMapping.tsx b/src/utils/components/PolicyForm/PolicyFormOVSBridgeMapping.tsx new file mode 100644 index 00000000..66184816 --- /dev/null +++ b/src/utils/components/PolicyForm/PolicyFormOVSBridgeMapping.tsx @@ -0,0 +1,54 @@ +import React, { FC } from 'react'; +import { Updater } from 'use-immer'; + +import { Button, ButtonVariant, Popover, Text } from '@patternfly/react-core'; +import { HelpIcon, PlusCircleIcon } from '@patternfly/react-icons'; +import { V1NodeNetworkConfigurationPolicy } from '@types'; +import { useNMStateTranslation } from '@utils/hooks/useNMStateTranslation'; + +import PolicyFormOVSBridgeMappingExpandable from './PolicyFormOVSBridgeMappingExpandable'; + +type PolicyFormOVSBridgeMappingProps = { + policy: V1NodeNetworkConfigurationPolicy; + setPolicy: Updater; +}; + +const PolicyFormOVSBridgeMapping: FC = ({ policy, setPolicy }) => { + const { t } = useNMStateTranslation(); + + return ( +
+ + {t('Open vSwitch bridge mapping')}{' '} + + + + + + + + +
+ ); +}; + +export default PolicyFormOVSBridgeMapping; diff --git a/src/utils/components/PolicyForm/PolicyFormOVSBridgeMappingExpandable.tsx b/src/utils/components/PolicyForm/PolicyFormOVSBridgeMappingExpandable.tsx new file mode 100644 index 00000000..c30c4698 --- /dev/null +++ b/src/utils/components/PolicyForm/PolicyFormOVSBridgeMappingExpandable.tsx @@ -0,0 +1,76 @@ +import React, { FC, FormEvent } from 'react'; +import { Updater } from 'use-immer'; + +import { + Button, + ButtonVariant, + Flex, + FlexItem, + PageSection, + PageSectionVariants, + TextInput, + Title, +} from '@patternfly/react-core'; +import { MinusCircleIcon } from '@patternfly/react-icons'; +import { V1NodeNetworkConfigurationPolicy } from '@types'; +import { useNMStateTranslation } from '@utils/hooks/useNMStateTranslation'; + +type PolicyFormOVSBridgeMappingExpandableProps = { + policy: V1NodeNetworkConfigurationPolicy; + setPolicy: Updater; +}; + +const PolicyFormOVSBridgeMappingExpandable: FC = ({ + policy, + setPolicy, +}) => { + const { t } = useNMStateTranslation(); + + const onChange = + (index: number, field: string) => (_: FormEvent, value: string) => { + setPolicy((draftPolicy) => { + draftPolicy.spec.desiredState.ovn['bridge-mapping'][index] = { + ...draftPolicy.spec.desiredState.ovn['bridge-mapping'][index], + [field]: value, + }; + }); + }; + + const onRemove = (index: number) => { + setPolicy((draftPolicy) => { + draftPolicy.spec.desiredState.ovn['bridge-mapping'].splice(index, 1); + }); + }; + + return policy?.spec?.desiredState?.ovn?.['bridge-mapping']?.map((bridgeMapping, index) => { + return ( + + + + + {t('OVN localnet name')} + + + + + + {t('OVS bridge name')} + + + + + + + + + ); + }); +}; + +export default PolicyFormOVSBridgeMappingExpandable; diff --git a/src/utils/components/PolicyForm/PolicyInterface.tsx b/src/utils/components/PolicyForm/PolicyInterface.tsx index c47bbd0d..62f3c7f9 100644 --- a/src/utils/components/PolicyForm/PolicyInterface.tsx +++ b/src/utils/components/PolicyForm/PolicyInterface.tsx @@ -29,14 +29,18 @@ import { AUTO_ROUTES, InterfaceType, NodeNetworkConfigurationInterface, + V1NodeNetworkConfigurationPolicy, } from '@types'; import BondOptions from './BondOptions'; import { DEFAULT_PREFIX_LENGTH, INTERFACE_TYPE_OPTIONS, NETWORK_STATES } from './constants'; import CopyMAC from './CopyMAC'; -import { validateInterfaceName } from './utils'; +import { isOVSBridgeExisting, validateInterfaceName } from './utils'; -export type onInterfaceChangeType = (policyInterface: NodeNetworkConfigurationInterface) => void; +export type onInterfaceChangeType = ( + policyInterface: NodeNetworkConfigurationInterface, + policy: V1NodeNetworkConfigurationPolicy, +) => void; type HandleSelectChange = FormSelectProps['onChange']; @@ -64,14 +68,30 @@ const PolicyInterface: FC = ({ }; const handleTypechange: HandleSelectChange = (event, newType: string) => { - onInterfaceChange((draftInterface) => { + onInterfaceChange((draftInterface, draftPolicy) => { draftInterface.type = newType as InterfaceType; + !isOVSBridgeExisting(draftPolicy) && delete draftPolicy.spec.desiredState.ovn; if (newType === InterfaceType.LINUX_BRIDGE) { delete draftInterface['link-aggregation']; draftInterface.bridge = { port: [], options: {} }; } + if (newType === InterfaceType.OVS_BRIDGE) { + delete draftInterface['link-aggregation']; + draftInterface.bridge = { port: [], options: {} }; + if (!draftPolicy?.spec?.desiredState?.ovn) { + draftPolicy.spec.desiredState.ovn = { + 'bridge-mapping': [], + }; + } + draftPolicy.spec.desiredState.ovn['bridge-mapping'].push({ + bridge: '', + localnet: '', + state: 'present', + }); + } + if (newType === InterfaceType.BOND) { delete draftInterface.bridge; draftInterface['link-aggregation'] = { @@ -155,7 +175,10 @@ const PolicyInterface: FC = ({ : delete draftInterface['link-aggregation'].port; } - if (draftInterface.type === InterfaceType.LINUX_BRIDGE) { + if ( + draftInterface.type === InterfaceType.LINUX_BRIDGE || + draftInterface.type === InterfaceType.OVS_BRIDGE + ) { ensurePath(draftInterface, 'bridge.port'); value ? (draftInterface.bridge.port = [{ name: value }]) @@ -346,7 +369,8 @@ const PolicyInterface: FC = ({ )} )} - {policyInterface.type === InterfaceType.LINUX_BRIDGE && ( + {(policyInterface.type === InterfaceType.LINUX_BRIDGE || + policyInterface.type === InterfaceType.OVS_BRIDGE) && ( = ({ interfaceIndex, 1, ); + !isOVSBridgeExisting(draftPolicy) && delete draftPolicy.spec.desiredState.ovn; }); }; @@ -95,7 +96,7 @@ const PolicyInterfacesExpandable: FC = ({ policyInterface={policyInterface} onInterfaceChange={(updateInterface: onInterfaceChangeType) => setPolicy((draftPolicy) => { - updateInterface(draftPolicy.spec.desiredState.interfaces[index]); + updateInterface(draftPolicy.spec.desiredState.interfaces[index], draftPolicy); }) } /> diff --git a/src/utils/components/PolicyForm/constants.ts b/src/utils/components/PolicyForm/constants.ts index 879ac8d9..74066908 100644 --- a/src/utils/components/PolicyForm/constants.ts +++ b/src/utils/components/PolicyForm/constants.ts @@ -7,7 +7,8 @@ export enum NETWORK_STATES { } export const INTERFACE_TYPE_OPTIONS = { - [InterfaceType.LINUX_BRIDGE]: 'Bridge', + [InterfaceType.LINUX_BRIDGE]: 'Linux bridge', + [InterfaceType.OVS_BRIDGE]: 'Open vSwitch bridge', [InterfaceType.BOND]: 'Bonding', [InterfaceType.ETHERNET]: 'Ethernet', }; diff --git a/src/utils/components/PolicyForm/utils.ts b/src/utils/components/PolicyForm/utils.ts index 219e2be8..34673f51 100644 --- a/src/utils/components/PolicyForm/utils.ts +++ b/src/utils/components/PolicyForm/utils.ts @@ -1,6 +1,10 @@ import { TFunction } from 'react-i18next'; -import { NodeNetworkConfigurationInterface } from '@types'; +import { + InterfaceType, + NodeNetworkConfigurationInterface, + V1NodeNetworkConfigurationPolicy, +} from '@types'; import { t } from '@utils/hooks/useNMStateTranslation'; import { INTERFACE_TYPE_OPTIONS } from './constants'; @@ -17,6 +21,11 @@ export const getExpandableTitle = ( return t('Policy interface'); }; +export const isOVSBridgeExisting = (policy: V1NodeNetworkConfigurationPolicy): boolean => + policy?.spec?.desiredState?.interfaces?.some( + (iface: NodeNetworkConfigurationInterface) => iface?.type === InterfaceType.OVS_BRIDGE, + ); + export const validateInterfaceName = (name: string): string => { if (!name) return ''; diff --git a/yarn.lock b/yarn.lock index f33686cb..41fa3fd8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -691,10 +691,10 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@kubevirt-ui/kubevirt-api@^0.0.47": - version "0.0.47" - resolved "https://registry.yarnpkg.com/@kubevirt-ui/kubevirt-api/-/kubevirt-api-0.0.47.tgz#763a5c1da69a64772545465c83e1bb9e386d1523" - integrity sha512-W+gZymVdT8Jb6alP8PRYlp7JMaki4gK8run45gw+2QOp7VNXDwT21CpZU/eANupftuhIH102hLITAjlvtIS/qA== +"@kubevirt-ui/kubevirt-api@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@kubevirt-ui/kubevirt-api/-/kubevirt-api-1.2.2.tgz#2a056159fb503d36bca94e62f9c2899aa2728202" + integrity sha512-8hcafV28OAnnNzQuCc5DOk/NCU8IR+h2/xCYW5gCZJFyziidWZwI3FAIcdalxQ9fgY9sgM2di65WeN5j/l4QNg== "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" @@ -722,10 +722,10 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@openshift-console/dynamic-plugin-sdk-webpack@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@openshift-console/dynamic-plugin-sdk-webpack/-/dynamic-plugin-sdk-webpack-1.0.1.tgz#0dbb0cf2f10526439249888c10c25223571f918a" - integrity sha512-xUv6obNPSp0T1Nc+ywywobQMvfxxsySraIejwnrjxBYelFMuMp3Tn/4c4ziUp/mftBTxPy0T4Nx4sLRB4gAPsw== +"@openshift-console/dynamic-plugin-sdk-webpack@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@openshift-console/dynamic-plugin-sdk-webpack/-/dynamic-plugin-sdk-webpack-1.1.1.tgz#349326366f84defa8c81baa8be411257d0b1e954" + integrity sha512-1m1iBpdj0HNWQIEFAhMlI5pmigyN5B/Eqcb2nGAI2n030hOOxAalxySO95dvEFa2hiyXMUt4z/Tyq4fDYKZ2nw== dependencies: "@openshift/dynamic-plugin-sdk-webpack" "^4.0.2" ajv "^6.12.3" @@ -738,10 +738,10 @@ semver "6.x" webpack "5.75.0" -"@openshift-console/dynamic-plugin-sdk@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@openshift-console/dynamic-plugin-sdk/-/dynamic-plugin-sdk-1.1.0.tgz#63b525aba54f01231921af23939cae0059e02eaf" - integrity sha512-gaNqhGq7nne+cC3QaCxuyK+SPvuC/K5Ww5tV2FabIepgbl3qKkUePrejPgvBJ52tMID1sn8prdW/rImYma6NwQ== +"@openshift-console/dynamic-plugin-sdk@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@openshift-console/dynamic-plugin-sdk/-/dynamic-plugin-sdk-1.4.0.tgz#802c1225f22a6d59b16b5b47f0ff81f176f2db72" + integrity sha512-LcwNqG2ZzMKLiSQNpHBwAtzALQghUngILhdijndpIklSH0Dsqi05nhwAJPyuFrpwywZIj6BxE1tmaBzR5gUfcA== dependencies: classnames "2.x" immutable "3.x" @@ -767,22 +767,23 @@ semver "^7.3.7" yup "^0.32.11" -"@openshift/dynamic-plugin-sdk-webpack@~1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@openshift/dynamic-plugin-sdk-webpack/-/dynamic-plugin-sdk-webpack-1.0.0.tgz#665fcda7601bee1c0718b7f8b75126c061e18c32" - integrity sha512-yEoqGSDLfaqGYKZseYMY/HvWzQIfUrQpQXjB+aMYv4hxxwF0dw3DAQYmZIauItJObGvMyqlxlCnTRMZCNtIcxg== +"@openshift/dynamic-plugin-sdk-webpack@~4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@openshift/dynamic-plugin-sdk-webpack/-/dynamic-plugin-sdk-webpack-4.1.0.tgz#eb62ee18d975431411b78da15c9774d95dbe1fa7" + integrity sha512-Pkq6R+fkoE0llgv9WJBcotViAPywrzDkpWK0HSTmrVyfEuWS5cuZUs8ono6L5w9BqDBRXm3ceEuUAZA/Zrar1w== dependencies: - glob "^7.2.0" lodash "^4.17.21" + semver "^7.3.7" yup "^0.32.11" -"@openshift/dynamic-plugin-sdk@~1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@openshift/dynamic-plugin-sdk/-/dynamic-plugin-sdk-1.0.0.tgz#7f05f7023a3eff7973067541fa0c23dcbcefd765" - integrity sha512-lkb2ZlbFXziPzVWOykfSAzeA1siGxMlzIDzd1wBh0RikjVr7a07U6gjrOcByaeC45AdUc2SNGRTERWoobj4THQ== +"@openshift/dynamic-plugin-sdk@~5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@openshift/dynamic-plugin-sdk/-/dynamic-plugin-sdk-5.0.1.tgz#3487afe57d07a28b9aba393e643d86ca13a3735c" + integrity sha512-+azUBN6FgcDmlcWMzG0bthcRUJC1u12wf9xa2aJGFbC/uTiOXwjrkcQ7LW/PyK5Em7wDhwaUdapaeOgh8I6Kjg== dependencies: - lodash-es "^4.17.21" + lodash "^4.17.21" semver "^7.3.7" + uuid "^8.3.2" yup "^0.32.11" "@patternfly/react-core@5.1.1": @@ -4002,7 +4003,7 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@7.x, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.2.0: +glob@7.x, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==