diff --git a/libs/ui-lib-tests/cypress/fixtures/dualstack/requests.ts b/libs/ui-lib-tests/cypress/fixtures/dualstack/requests.ts index 802756e06f..818e8015cb 100644 --- a/libs/ui-lib-tests/cypress/fixtures/dualstack/requests.ts +++ b/libs/ui-lib-tests/cypress/fixtures/dualstack/requests.ts @@ -60,6 +60,12 @@ export const dualStackNetworkingRequest = { }, ], user_managed_networking: false, - api_vips: [{ ip: '192.168.122.10', cluster_id: fakeClusterId }], - ingress_vips: [{ ip: '192.168.122.110', cluster_id: fakeClusterId }], + api_vips: [ + { ip: '192.168.122.10', cluster_id: fakeClusterId }, + { ip: '1001:db9::1', cluster_id: fakeClusterId }, + ], + ingress_vips: [ + { ip: '192.168.122.110', cluster_id: fakeClusterId }, + { ip: '1001:db9::2', cluster_id: fakeClusterId }, + ], }; diff --git a/libs/ui-lib-tests/cypress/integration/dualstack/2-networking.cy.ts b/libs/ui-lib-tests/cypress/integration/dualstack/2-networking.cy.ts index 81fc6e6c6f..30566b900e 100644 --- a/libs/ui-lib-tests/cypress/integration/dualstack/2-networking.cy.ts +++ b/libs/ui-lib-tests/cypress/integration/dualstack/2-networking.cy.ts @@ -47,6 +47,7 @@ describe(`Assisted Installer Dualstack Networking`, () => { networkingPage .getClusterSubnetCidrIpv6() .should('contain.text', '1001:db9::/120 (1001:db9:: - 1001:db9::ff)'); + networkingPage.inputApiVipIngressVipSecondary('1001:db9::1', '1001:db9::2'); networkingPage.waitForNetworkStatusToNotContain('Some validations failed'); cy.wait('@update-cluster').then(({ request }) => { diff --git a/libs/ui-lib-tests/cypress/views/networkingPage.ts b/libs/ui-lib-tests/cypress/views/networkingPage.ts index ec94ba609c..135587ffad 100644 --- a/libs/ui-lib-tests/cypress/views/networkingPage.ts +++ b/libs/ui-lib-tests/cypress/views/networkingPage.ts @@ -132,6 +132,12 @@ export const networkingPage = { getIngressVipField: () => { return cy.get('#form-input-ingressVips-0-ip-field'); }, + getApiVipFieldSecondary: () => { + return cy.get('#form-input-apiVips-1-ip-field'); + }, + getIngressVipFieldSecondary: () => { + return cy.get('#form-input-ingressVips-1-ip-field'); + }, inputApiVipIngressVip: ( apiVip = Cypress.env('API_VIP'), ingressVip = Cypress.env('INGRESS_VIP'), @@ -146,6 +152,20 @@ export const networkingPage = { fillField(networkingPage.getIngressVipField(), ingressVip); } }, + inputApiVipIngressVipSecondary: ( + apiVip = Cypress.env('API_VIP'), + ingressVip = Cypress.env('INGRESS_VIP'), + ) => { + const fillField = (element, value) => { + element.scrollIntoView().should('be.visible').fill(value).should('have.value', value); + }; + if (apiVip) { + fillField(networkingPage.getApiVipFieldSecondary(), apiVip); + } + if (ingressVip) { + fillField(networkingPage.getIngressVipFieldSecondary(), ingressVip); + } + }, inputClusterNetworkHostPrefix: (hostPrefix = Cypress.env('NETWORK_HOST_PREFIX')) => { cy.get(Cypress.env('clusterNetworks0HostPrefixFieldId')) .fill(hostPrefix) diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/networkConfiguration/VirtualIPControlGroup.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/networkConfiguration/VirtualIPControlGroup.tsx index 97da11f850..07a14b3b98 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/networkConfiguration/VirtualIPControlGroup.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/networkConfiguration/VirtualIPControlGroup.tsx @@ -14,6 +14,7 @@ import { NetworkConfigurationValues, FormikStaticField, NETWORK_TYPE_SDN, + DUAL_STACK, selectMachineNetworkCIDR, getVipValidationsById, PopoverIcon, @@ -24,7 +25,12 @@ import { selectCurrentClusterPermissionsState } from '../../../store/slices/curr import { OcmCheckboxField, OcmInputField } from '../../ui/OcmFormFields'; import { useTranslation } from '../../../../common/hooks/use-translation-wrapper'; import NewFeatureSupportLevelBadge from '../../../../common/components/newFeatureSupportLevels/NewFeatureSupportLevelBadge'; -import { Cluster, Ip, SupportLevel } from '@openshift-assisted/types/assisted-installer-service'; +import { + ApiVip, + Cluster, + Ip, + SupportLevel, +} from '@openshift-assisted/types/assisted-installer-service'; interface VipStaticValueProps { id?: string; @@ -96,6 +102,7 @@ export const VirtualIPControlGroup = ({ ); const enableAllocation = values.networkType === NETWORK_TYPE_SDN; + const isDualStack = values.stackType === DUAL_STACK; React.useEffect(() => { if (!isViewerMode && !enableAllocation) { @@ -112,8 +119,20 @@ export const VirtualIPControlGroup = ({ [cluster.apiVips, cluster.ingressVips, setFieldValue], ); - const setVipValue = (field: string, e: React.ChangeEvent) => { - setFieldValue(field, [{ ip: e.target.value, clusterId: cluster.id }], true); + const setVipValueAtIndex = ( + field: 'apiVips' | 'ingressVips', + index: number, + e: React.ChangeEvent, + ) => { + const fieldArray: ApiVip[] = + field === 'apiVips' ? values.apiVips || [] : values.ingressVips || []; + const next: ApiVip[] = Array.isArray(fieldArray) ? [...fieldArray] : []; + // Ensure array has the desired length + while (next.length <= index) { + next.push({ ip: '', clusterId: cluster.id }); + } + next[index] = { ip: e.target.value, clusterId: cluster.id }; + setFieldValue(field, next, true); }; return ( @@ -194,11 +213,30 @@ export const VirtualIPControlGroup = ({ name="apiVips.0.ip" helperText={ipHelperText} isRequired + labelInfo={isDualStack ? 'Primary' : undefined} onChange={(e) => - setVipValue('apiVips', e as React.ChangeEvent) + setVipValueAtIndex('apiVips', 0, e as React.ChangeEvent) } /> + {isDualStack && ( + + + API IP + + } + name="apiVips.1.ip" + helperText={ipHelperText} + isRequired + labelInfo={'Secondary'} + onChange={(e) => + setVipValueAtIndex('apiVips', 1, e as React.ChangeEvent) + } + /> + + )} - setVipValue('ingressVips', e as React.ChangeEvent) + setVipValueAtIndex('ingressVips', 0, e as React.ChangeEvent) } /> + {isDualStack && ( + + + Ingress IP + + } + helperText={ipHelperText} + isRequired + labelInfo={'Secondary'} + onChange={(e) => + setVipValueAtIndex( + 'ingressVips', + 1, + e as React.ChangeEvent, + ) + } + /> + + )} )} diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/networkConfiguration/networkConfigurationValidation.ts b/libs/ui-lib/lib/ocm/components/clusterConfiguration/networkConfiguration/networkConfigurationValidation.ts index 565b261e2d..bd261659c2 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/networkConfiguration/networkConfigurationValidation.ts +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/networkConfiguration/networkConfigurationValidation.ts @@ -74,9 +74,17 @@ export const getNetworkConfigurationValidationSchema = ( hostSubnets: HostSubnets, openshiftVersion?: string, ) => - Yup.lazy((values: NetworkConfigurationValues) => - Yup.object().shape({ - apiVips: vipArrayValidationSchema(hostSubnets, values, initialValues.apiVips), + Yup.lazy((values: NetworkConfigurationValues) => { + const apiVipSchema = + values.stackType === DUAL_STACK + ? vipArrayValidationSchema(hostSubnets, values, initialValues.apiVips).min( + 2, + 'Provide both Primary and Secondary API IPs.', + ) + : vipArrayValidationSchema(hostSubnets, values, initialValues.apiVips); + + return Yup.object().shape({ + apiVips: apiVipSchema, ingressVips: vipArrayValidationSchema( hostSubnets, values, @@ -116,5 +124,5 @@ export const getNetworkConfigurationValidationSchema = ( ) : Yup.array(), }), - }), - ); + }); + });