Skip to content

[WIP] CNV-52160: UDN with topology selector #230

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
7 changes: 6 additions & 1 deletion locales/en/plugin__networking-console-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,16 @@
"key is the label key that the selector applies": "key is the label key that the selector applies",
"Kind": "Kind",
"Labels": "Labels",
"Layer 2": "Layer 2",
"Layer 3": "Layer 3",
"Layer2 topology creates one logical switch shared by all nodes.": "Layer2 topology creates one logical switch shared by all nodes.",
"Layer3 topology creates a layer 2 segment per node, each with a different subnet. Layer 3 routing is used to interconnect node subnets.": "Layer3 topology creates a layer 2 segment per node, each with a different subnet. Layer 3 routing is used to interconnect node subnets.",
"Learn how to use NetworkAttachmentDefinitions": "Learn how to use NetworkAttachmentDefinitions",
"Learn more about {{ kind }}": "Learn more about {{ kind }}",
"Learn more about working with projects": "Learn more about working with projects",
"List of pods": "List of pods",
"List of pods matching": "List of pods matching",
"Localnet": "Localnet",
"Location": "Location",
"Location of the resource that backs the service": "Location of the resource that backs the service",
"MAC spoof check": "MAC spoof check",
Expand Down Expand Up @@ -270,6 +273,7 @@
"Percent": "Percent",
"Persistent": "Persistent",
"Phase": "Phase",
"Physical network name": "Physical network name",
"Physical network name. A bridge mapping must be configured on cluster nodes to map between physical network names and Open vSwitch bridges.": "Physical network name. A bridge mapping must be configured on cluster nodes to map between physical network names and Open vSwitch bridges.",
"Please <2>try again</2>.": "Please <2>try again</2>.",
"Pod crash loop back-off": "Pod crash loop back-off",
Expand Down Expand Up @@ -352,7 +356,7 @@
"Sources added to this rule will allow traffic to the pods defined above. Sources in this list are combined using a logical OR operation.": "Sources added to this rule will allow traffic to the pods defined above. Sources in this list are combined using a logical OR operation.",
"Status": "Status",
"Subnet": "Subnet",
"Subnet CIRD": "Subnet CIRD",
"Subnet CIDR": "Subnet CIDR",
"Subnets": "Subnets",
"Subnets are used for the pod network across the cluster.": "Subnets are used for the pod network across the cluster.",
"Switch and delete": "Switch and delete",
Expand All @@ -364,6 +368,7 @@
"Terminating": "Terminating",
"Termination type": "Termination type",
"The format should match standard CIDR notation (for example, \"10.128.0.0/16\").": "The format should match standard CIDR notation (for example, \"10.128.0.0/16\").",
"The name of the physical network. This attribute must match the value of the spec.desiredState.ovn.bridge-mappings.localnet field of the NodeNetworkConfigurationPolicy object that defines the OVS bridge mapping. ": "The name of the physical network. This attribute must match the value of the spec.desiredState.ovn.bridge-mappings.localnet field of the NodeNetworkConfigurationPolicy object that defines the OVS bridge mapping. ",
"The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an \"ipamclaims.k8s.cni.cncf.io\" object.": "The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an \"ipamclaims.k8s.cni.cncf.io\" object.",
"These IP addresses will be reused by other pods if requested.": "These IP addresses will be reused by other pods if requested.",
"These rules are handled by a routing layer (Ingress Controller) which is updated as the rules are modified. The Ingress controller implementation defines how headers and other metadata are forwarded or manipulated": "These rules are handled by a routing layer (Ingress Controller) which is updated as the rules are modified. The Ingress controller implementation defines how headers and other metadata are forwarded or manipulated",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useNetworkingTranslation } from '@utils/hooks/useNetworkingTranslation'

import FormGroupHelperText from '../FormGroupHelperText/FormGroupHelperText';

const SubnetCIRDHelperText: FC = () => {
const SubnetCIDRHelperText: FC = () => {
const { t } = useNetworkingTranslation();

return (
Expand All @@ -16,4 +16,4 @@ const SubnetCIRDHelperText: FC = () => {
);
};

export default SubnetCIRDHelperText;
export default SubnetCIDRHelperText;
4 changes: 4 additions & 0 deletions src/utils/resources/udns/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export const PROJECT_LABEL_FOR_MATCH_EXPRESSION = 'kubernetes.io/metadata.name';

export const FIXED_PRIMARY_UDN_NAME = 'primary-udn';

export const LOCALNET_TOPOLOGY = 'Localnet';
export const LAYER2_TOPOLOGY = 'Layer2';
export const LAYER3_TOPOLOGY = 'Layer3';
7 changes: 6 additions & 1 deletion src/utils/resources/udns/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export type UserDefinedNetworkSubnet = string | UserDefinedNetworkLayer3Subnet;
export type UserDefinedNetworkLayer3 = {
joinSubnets?: string[];
mtu?: number;
role: string;
role: UserDefinedNetworkRole;
subnets?: UserDefinedNetworkLayer3Subnet[];
};

Expand All @@ -41,6 +41,11 @@ export type ClusterUserDefinedNetworkSpec = {
export type UserDefinedNetworkSpec = {
layer2?: UserDefinedNetworkLayer2;
layer3?: UserDefinedNetworkLayer3;
localnet?: {
physicalNetworkName: string;
role: UserDefinedNetworkRole;
subnets?: UserDefinedNetworkSubnet[];
};
topology: string;
};

Expand Down
2 changes: 1 addition & 1 deletion src/views/createprojectmodal/components/NetworkTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const NetworkTab: FC = () => {

{networkType === NETWORK_TYPE.UDN && (
<>
<FormGroup fieldId="input-name" isRequired label={t('Subnet CIRD')}>
<FormGroup fieldId="input-name" isRequired label={t('Subnet CIDR')}>
<Controller
control={control}
name="udn.spec.layer2.subnets"
Expand Down
62 changes: 62 additions & 0 deletions src/views/udns/list/components/SubnetsInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { FC } from 'react';
import { FieldPath, useFormContext, useWatch } from 'react-hook-form';

import { FormGroup, TextInput } from '@patternfly/react-core';
import SubnetCIDRHelperText from '@utils/components/SubnetCIDRHelperText/SubnetCIDRHelperText';
import { useNetworkingTranslation } from '@utils/hooks/useNetworkingTranslation';
import {
UserDefinedNetworkLayer3Subnet,
UserDefinedNetworkSpec,
} from '@utils/resources/udns/types';

import { UDNForm } from './constants';
import { getSubnetFields, getSubnetsFromNetworkSpec } from './utils';

type SubnetsInputProps = {
isClusterUDN?: boolean;
};

const SubnetsInput: FC<SubnetsInputProps> = ({ isClusterUDN }) => {
const { t } = useNetworkingTranslation();

const { setValue } = useFormContext<UDNForm>();
const networkSpecPath = isClusterUDN ? 'spec.network' : 'spec';

const networkSpec: UserDefinedNetworkSpec = useWatch<UDNForm>({
name: networkSpecPath,
});

const subnets = getSubnetsFromNetworkSpec(networkSpec);

const subnetsText = networkSpec.layer3
? (subnets as UserDefinedNetworkLayer3Subnet[]).map((subnet) => subnet.cidr).join(',')
: subnets?.join(',');

return (
<FormGroup fieldId="input-udn-subnet" isRequired label={t('Subnet CIDR')}>
<TextInput
autoFocus
data-test="input-udn-subnet"
id="input-udn-subnet"
isRequired
name="input-udn-subnet"
onChange={(_, newValue) => {
const subnetField = getSubnetFields(networkSpec, isClusterUDN);

const newSubnet = networkSpec.layer3
? newValue.split(',').map((subnet) => ({ cidr: subnet }))
: newValue.split(',');

setValue(subnetField as FieldPath<UDNForm>, newSubnet, {
shouldValidate: true,
});
}}
type="text"
value={subnetsText}
/>
<SubnetCIDRHelperText />
</FormGroup>
);
};

export default SubnetsInput;
130 changes: 130 additions & 0 deletions src/views/udns/list/components/Topology.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import React, { FC, Ref, useState } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';

import {
DropdownItem,
Flex,
FlexItem,
FormGroup,
MenuToggle,
MenuToggleElement,
Radio,
Select,
TextInput,
} from '@patternfly/react-core';
import FormGroupHelperText from '@utils/components/FormGroupHelperText/FormGroupHelperText';
import { useNetworkingTranslation } from '@utils/hooks/useNetworkingTranslation';
import { LOCALNET_TOPOLOGY } from '@utils/resources/udns/constants';
import { UserDefinedNetworkRole, UserDefinedNetworkSpec } from '@utils/resources/udns/types';

import { UDNForm } from './constants';
import { createNetworkSpecFromRole, createNetworkSpecFromTopology, getTopology } from './utils';

type TopologyProps = {
isClusterUDN: boolean;
};

const Topology: FC<TopologyProps> = ({ isClusterUDN }) => {
const { t } = useNetworkingTranslation();
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const { register, setValue } = useFormContext<UDNForm>();

const networkSpecPath = isClusterUDN ? 'spec.network' : 'spec';

const networkSpec: UserDefinedNetworkSpec = useWatch<UDNForm>({
name: networkSpecPath,
});

const topology = getTopology(networkSpec);

const isPrimary =
networkSpec?.layer2?.role === UserDefinedNetworkRole.Primary ||
networkSpec?.layer3?.role === UserDefinedNetworkRole.Primary;

const onChangeRole = (role: UserDefinedNetworkRole) => {
setValue(networkSpecPath, createNetworkSpecFromRole(networkSpec, role));
};

return (
<>
<FormGroup>
<Flex>
<FlexItem>
<Radio
id="primary-topology"
isChecked={isPrimary}
label="Primary"
name="radio-topology"
onChange={() => onChangeRole(UserDefinedNetworkRole.Primary)}
></Radio>
</FlexItem>
<FlexItem>
<Radio
id="secondary-topology"
isChecked={!isPrimary}
label="Secondary"
name="radio-topology"
onChange={() => onChangeRole(UserDefinedNetworkRole.Secondary)}
></Radio>
</FlexItem>
</Flex>
</FormGroup>
<FormGroup fieldId="select-topology" isRequired label={t('Topology')}>
<Select
id="select-topology"
isOpen={isDropdownOpen}
onOpenChange={setIsDropdownOpen}
onSelect={(_, selection) => {
setValue(
isClusterUDN ? 'spec.network' : 'spec',
createNetworkSpecFromTopology(selection as string, networkSpec),
);
setIsDropdownOpen(false);
}}
selected={topology}
toggle={(toggleRef: Ref<MenuToggleElement>) => (
<MenuToggle
id="toggle-udns-operator"
isDisabled={!isPrimary}
isExpanded={isDropdownOpen}
isFullWidth
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
ref={toggleRef}
>
{topology}
</MenuToggle>
)}
>
<>
{isPrimary ? (
<>
<DropdownItem value="Layer2">{t('Layer 2')}</DropdownItem>
<DropdownItem value="Layer3">{t('Layer 3')}</DropdownItem>
</>
) : (
<DropdownItem value="Locanet">{t('Localnet')}</DropdownItem>
)}
</>
</Select>
</FormGroup>

{topology === LOCALNET_TOPOLOGY && (
<FormGroup fieldId="input-name" isRequired label={t('Physical network name')}>
<TextInput
autoFocus
{...register('spec.localnet.physicalNetworkName', { required: true })}
isRequired
/>

<FormGroupHelperText>
{t(
'The name of the physical network. This attribute must match the value of the spec.desiredState.ovn.bridge-mappings.localnet field of the NodeNetworkConfigurationPolicy object that defines the OVS bridge mapping. ',
)}
</FormGroupHelperText>
</FormGroup>
)}
</>
);
};

export default Topology;
46 changes: 7 additions & 39 deletions src/views/udns/list/components/UserDefinedNetworkCreateForm.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
import React, { FC, FormEventHandler } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { useFormContext } from 'react-hook-form';

import { Alert, AlertVariant, Content, Form, FormGroup, TextInput } from '@patternfly/react-core';
import SubnetCIRDHelperText from '@utils/components/SubnetCIRDHelperText/SubnetCIRDHelperText';
import { Content, Form, FormGroup, TextInput } from '@patternfly/react-core';
import { useNetworkingTranslation } from '@utils/hooks/useNetworkingTranslation';

import ClusterUDNNamespaceSelector from './ClusterUDNNamespaceSelector';
import { UDNForm } from './constants';
import SelectProject from './SelectProject';
import SubnetsInput from './SubnetsInput';
import Topology from './Topology';

type UserDefinedNetworkCreateFormProps = {
error: Error;
isClusterUDN?: boolean;
onSubmit: FormEventHandler<HTMLFormElement>;
};

const UserDefinedNetworkCreateForm: FC<UserDefinedNetworkCreateFormProps> = ({
error,
isClusterUDN,
onSubmit,
}) => {
const { t } = useNetworkingTranslation();

const { control, register, setValue } = useFormContext<UDNForm>();

const subnetField = isClusterUDN ? 'spec.network.layer2.subnets' : 'spec.layer2.subnets';
const { register } = useFormContext<UDNForm>();

return (
<Form id="create-udn-form" onSubmit={onSubmit}>
Expand All @@ -48,39 +45,10 @@ const UserDefinedNetworkCreateForm: FC<UserDefinedNetworkCreateFormProps> = ({
</FormGroup>
)}

<FormGroup fieldId="input-udn-subnet" isRequired label={t('Subnet CIRD')}>
<Controller
control={control}
name={subnetField}
render={({ field: { value } }) => (
<TextInput
autoFocus
data-test="input-udn-subnet"
id="input-udn-subnet"
isRequired
name="input-udn-subnet"
onChange={(_, newValue) =>
setValue(subnetField, newValue.split(','), {
shouldValidate: true,
})
}
type="text"
value={value?.join(',')}
/>
)}
rules={{ required: true }}
/>

<SubnetCIRDHelperText />
</FormGroup>
<SubnetsInput isClusterUDN={isClusterUDN} />

<Topology isClusterUDN={isClusterUDN} />
{isClusterUDN && <ClusterUDNNamespaceSelector />}

{error && (
<Alert isInline title={t('Error')} variant={AlertVariant.danger}>
{error?.message}
</Alert>
)}
</Form>
);
};
Expand Down
Loading