Skip to content
This repository has been archived by the owner on Nov 28, 2024. It is now read-only.

Commit

Permalink
CNV-38365: OVN bridge mapping
Browse files Browse the repository at this point in the history
Signed-off-by: Matan Schatzman <[email protected]>
  • Loading branch information
metalice committed Jul 16, 2024
1 parent e8e378e commit edfd3b2
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 39 deletions.
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

0 comments on commit edfd3b2

Please sign in to comment.