Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CNV-38365: OVN bridge mapping #107

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions locales/en/plugin__nmstate-console-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"Actions": "Actions",
"Add another interface to the policy": "Add another interface to the policy",
"Add Label": "Add Label",
"Add mapping": "Add mapping",
"Add Option": "Add Option",
"Aggregation mode": "Aggregation mode",
"An error occurred": "An error occurred",
Expand Down Expand Up @@ -96,6 +97,9 @@
"NodeNetworkState": "NodeNetworkState",
"nodes": "nodes",
"None": "None",
"Open vSwitch bridge mapping": "Open vSwitch bridge mapping",
"OVN localnet name": "OVN localnet name",
"OVS bridge name": "OVS bridge name",
"Pending": "Pending",
"Please <2>try again</2>.": "Please <2>try again</2>.",
"Policy details": "Policy details",
Expand Down Expand Up @@ -124,6 +128,7 @@
"Server": "Server",
"System Description": "System Description",
"System Name": "System Name",
"The Open vSwitch bridge mapping is a list of Open vSwitch bridges and the physical interfaces that are connected to them.": "The Open vSwitch bridge mapping is a list of Open vSwitch bridges and the physical interfaces that are connected to them.",
"This is the list of ports to copy MAC address from. Select one of the matched ports this policy will apply to": "This is the list of ports to copy MAC address from. Select one of the matched ports this policy will apply to",
"This node already has a policy matching it": "This node already has a policy matching it",
"This policy must be edited via YAML": "This policy must be edited via YAML",
Expand Down
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 5 additions & 1 deletion scripts/start-console.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 4 additions & 1 deletion src/utils/components/PolicyForm/PolicyForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -28,6 +30,7 @@ type PolicyFormProps = {
const PolicyForm: FC<PolicyFormProps> = ({ policy, setPolicy, createForm = false, formId }) => {
const { t } = useNMStateTranslation();
const [modalOpen, setModalOpen] = useState(false);
const isOVSBridge = isOVSBridgeExisting(policy);

const onDescriptionChange = (newDescription: string) => {
setPolicy(({ metadata }) => {
Expand Down Expand Up @@ -137,9 +140,9 @@ const PolicyForm: FC<PolicyFormProps> = ({ policy, setPolicy, createForm = false
<span className="pf-u-ml-sm">{t('Add another interface to the policy')}</span>
</Button>
</Text>

<PolicyInterfacesExpandable policy={policy} setPolicy={setPolicy} createForm={createForm} />
</div>
{isOVSBridge && <PolicyFormOVSBridgeMapping policy={policy} setPolicy={setPolicy} />}
</Form>
);
};
Expand Down
54 changes: 54 additions & 0 deletions src/utils/components/PolicyForm/PolicyFormOVSBridgeMapping.tsx
Original file line number Diff line number Diff line change
@@ -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<V1NodeNetworkConfigurationPolicy>;
};

const PolicyFormOVSBridgeMapping: FC<PolicyFormOVSBridgeMappingProps> = ({ policy, setPolicy }) => {
const { t } = useNMStateTranslation();

return (
<div>
<Text className="pf-u-primary-color-100 pf-u-font-weight-bold pf-u-font-size-lg">
{t('Open vSwitch bridge mapping')}{' '}
<Popover
aria-label={'Help'}
bodyContent={t(
'The Open vSwitch bridge mapping is a list of Open vSwitch bridges and the physical interfaces that are connected to them.',
)}
>
<HelpIcon />
</Popover>
</Text>
<Text className="policy-form-content__add-new-interface pf-u-mt-md">
<Button
className="pf-m-link--align-left pf-u-ml-md"
onClick={() =>
setPolicy((draftPolicy) => {
draftPolicy.spec.desiredState.ovn['bridge-mapping'].unshift({
bridge: '',
localnet: '',
state: 'present',
});
})
}
variant={ButtonVariant.link}
>
<PlusCircleIcon /> <span className="pf-u-ml-sm">{t('Add mapping')}</span>
</Button>
</Text>
<PolicyFormOVSBridgeMappingExpandable policy={policy} setPolicy={setPolicy} />
</div>
);
};

export default PolicyFormOVSBridgeMapping;
Original file line number Diff line number Diff line change
@@ -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<V1NodeNetworkConfigurationPolicy>;
};

const PolicyFormOVSBridgeMappingExpandable: FC<PolicyFormOVSBridgeMappingExpandableProps> = ({
policy,
setPolicy,
}) => {
const { t } = useNMStateTranslation();

const onChange =
(index: number, field: string) => (_: FormEvent<HTMLInputElement>, 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 (
<PageSection variant={PageSectionVariants.light} key={index}>
<Flex alignItems={{ default: 'alignItemsFlexEnd' }} marginWidth={20}>
<FlexItem grow={{ default: 'grow' }} spacer={{ default: 'spacer4xl' }}>
<Title headingLevel="h6" size="md">
{t('OVN localnet name')}
</Title>
<TextInput value={bridgeMapping?.localnet} onChange={onChange(index, 'localnet')} />
</FlexItem>
<FlexItem grow={{ default: 'grow' }}>
<Title headingLevel="h6" size="md">
{t('OVS bridge name')}
</Title>
<TextInput value={bridgeMapping?.bridge} onChange={onChange(index, 'bridge')} />
</FlexItem>
<FlexItem>
<Button
variant={ButtonVariant.plain}
aria-label={t('Remove')}
onClick={() => onRemove(index)}
>
<MinusCircleIcon />
</Button>
</FlexItem>
</Flex>
</PageSection>
);
});
};

export default PolicyFormOVSBridgeMappingExpandable;
34 changes: 29 additions & 5 deletions src/utils/components/PolicyForm/PolicyInterface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'];

Expand Down Expand Up @@ -64,14 +68,30 @@ const PolicyInterface: FC<PolicyInterfaceProps> = ({
};

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'] = {
Expand Down Expand Up @@ -155,7 +175,10 @@ const PolicyInterface: FC<PolicyInterfaceProps> = ({
: 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 }])
Expand Down Expand Up @@ -346,7 +369,8 @@ const PolicyInterface: FC<PolicyInterfaceProps> = ({
)}
</FormGroup>
)}
{policyInterface.type === InterfaceType.LINUX_BRIDGE && (
{(policyInterface.type === InterfaceType.LINUX_BRIDGE ||
policyInterface.type === InterfaceType.OVS_BRIDGE) && (
<FormGroup fieldId={`policy-interface-stp-${id}`}>
<Checkbox
label={
Expand Down
5 changes: 3 additions & 2 deletions src/utils/components/PolicyForm/PolicyInterfaceExpandable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { useNMStateTranslation } from '@utils/hooks/useNMStateTranslation';

import DeleteInterfaceModal from './DeleteInterfaceModal';
import PolicyInterface, { onInterfaceChangeType } from './PolicyInterface';
import { getExpandableTitle } from './utils';
import { getExpandableTitle, isOVSBridgeExisting } from './utils';

type PolicyInterfacesExpandableProps = {
policy: V1NodeNetworkConfigurationPolicy;
Expand Down Expand Up @@ -47,6 +47,7 @@ const PolicyInterfacesExpandable: FC<PolicyInterfacesExpandableProps> = ({
interfaceIndex,
1,
);
!isOVSBridgeExisting(draftPolicy) && delete draftPolicy.spec.desiredState.ovn;
});
};

Expand Down Expand Up @@ -95,7 +96,7 @@ const PolicyInterfacesExpandable: FC<PolicyInterfacesExpandableProps> = ({
policyInterface={policyInterface}
onInterfaceChange={(updateInterface: onInterfaceChangeType) =>
setPolicy((draftPolicy) => {
updateInterface(draftPolicy.spec.desiredState.interfaces[index]);
updateInterface(draftPolicy.spec.desiredState.interfaces[index], draftPolicy);
})
}
/>
Expand Down
3 changes: 2 additions & 1 deletion src/utils/components/PolicyForm/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
};
Expand Down
11 changes: 10 additions & 1 deletion src/utils/components/PolicyForm/utils.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 '';

Expand Down
Loading
Loading