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

[8.x] Validate max number of node connections (#196908) #197100

Merged
merged 1 commit into from
Oct 21, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { act } from 'react-dom/test-utils';
import { setupEnvironment, RemoteClustersActions } from '../helpers';
import { setup } from './remote_clusters_add.helpers';
import { NON_ALPHA_NUMERIC_CHARS, ACCENTED_CHARS } from './special_characters';
import { MAX_NODE_CONNECTIONS } from '../../../common/constants';

const notInArray = (array: string[]) => (value: string) => array.indexOf(value) < 0;

Expand Down Expand Up @@ -276,6 +277,17 @@ describe('Create Remote cluster', () => {
});
});

describe('node connections', () => {
test('should require a valid number of node connections', async () => {
await actions.saveButton.click();

actions.nodeConnectionsInput.setValue(String(MAX_NODE_CONNECTIONS + 1));
expect(actions.getErrorMessages()).toContain(
`This number must be equal or less than ${MAX_NODE_CONNECTIONS}.`
);
});
});

test('server name is optional (proxy connection)', () => {
actions.connectionModeSwitch.toggle();
actions.saveButton.click();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export interface RemoteClustersActions {
setValue: (seed: string) => void;
getValue: () => string;
};
nodeConnectionsInput: {
setValue: (connections: string) => void;
};
proxyAddressInput: {
setValue: (proxyAddress: string) => void;
exists: () => boolean;
Expand Down Expand Up @@ -154,6 +157,16 @@ export const createRemoteClustersActions = (testBed: TestBed): RemoteClustersAct
};
};

const createNodeConnectionsInputActions = () => {
const nodeConnectionsInputSelector = 'remoteClusterFormNodeConnectionsInput';
return {
nodeConnectionsInput: {
setValue: (connections: string) =>
form.setInputValue(nodeConnectionsInputSelector, connections),
},
};
};

const createProxyAddressActions = () => {
const proxyAddressSelector = 'remoteClusterFormProxyAddressInput';
return {
Expand Down Expand Up @@ -266,6 +279,7 @@ export const createRemoteClustersActions = (testBed: TestBed): RemoteClustersAct
...createConnectionModeActions(),
...createCloudAdvancedOptionsSwitchActions(),
...createSeedsInputActions(),
...createNodeConnectionsInputActions(),
...createCloudRemoteAddressInputActions(),
...createProxyAddressActions(),
...createServerNameActions(),
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/remote_clusters/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ export const getSecurityModel = (type: string) => {

return type;
};

// Hardcoded limit of maximum node connections allowed
export const MAX_NODE_CONNECTIONS = 2 ** 31 - 1; // 2147483647
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,16 @@ export const SniffConnection: FunctionComponent<Props> = ({
}) => {
const [localSeedErrors, setLocalSeedErrors] = useState<JSX.Element[]>([]);
const { seeds = [], nodeConnections } = fields;
const { seeds: seedsError } = fieldsErrors;
const { seeds: seedsError, nodeConnections: nodeError } = fieldsErrors;
// Show errors if there is a general form error or local errors.
const areFormErrorsVisible = Boolean(areErrorsVisible && seedsError);
const showErrors = areFormErrorsVisible || localSeedErrors.length !== 0;
const errors =
const showLocalSeedErrors = areFormErrorsVisible || localSeedErrors.length !== 0;
const errorsInLocalSeeds =
areFormErrorsVisible && seedsError ? localSeedErrors.concat(seedsError) : localSeedErrors;
const formattedSeeds: EuiComboBoxOptionOption[] = seeds.map((seed: string) => ({ label: seed }));

const showNodeConnectionErrors = Boolean(nodeError);

const onCreateSeed = (newSeed?: string) => {
// If the user just hit enter without typing anything, treat it as a no-op.
if (!newSeed) {
Expand Down Expand Up @@ -107,8 +109,8 @@ export const SniffConnection: FunctionComponent<Props> = ({
}}
/>
}
isInvalid={showErrors}
error={errors}
isInvalid={showLocalSeedErrors}
error={errorsInLocalSeeds}
fullWidth
>
<EuiComboBox
Expand All @@ -125,7 +127,7 @@ export const SniffConnection: FunctionComponent<Props> = ({
onFieldsChange({ seeds: options.map(({ label }) => label) })
}
onSearchChange={onSeedsInputChange}
isInvalid={showErrors}
isInvalid={showLocalSeedErrors}
fullWidth
data-test-subj="remoteClusterFormSeedsInput"
/>
Expand All @@ -146,11 +148,15 @@ export const SniffConnection: FunctionComponent<Props> = ({
/>
}
fullWidth
isInvalid={showNodeConnectionErrors}
error={nodeError}
>
<EuiFieldNumber
value={nodeConnections || ''}
onChange={(e) => onFieldsChange({ nodeConnections: Number(e.target.value) })}
isInvalid={showNodeConnectionErrors}
fullWidth
data-test-subj="remoteClusterFormNodeConnectionsInput"
/>
</EuiFormRow>
</>
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export {
validateCloudRemoteAddress,
convertCloudRemoteAddressToProxyConnection,
} from './validate_cloud_url';
export { validateNodeConnections } from './validate_node_connections';
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { validateSeeds } from './validate_seeds';
import { validateProxy } from './validate_proxy';
import { validateCloudRemoteAddress } from './validate_cloud_url';
import { FormFields } from '../remote_cluster_form';
import { validateNodeConnections } from './validate_node_connections';

type ClusterError = JSX.Element | null;

Expand All @@ -19,14 +20,16 @@ export interface ClusterErrors {
seeds?: ClusterError;
proxyAddress?: ClusterError;
cloudRemoteAddress?: ClusterError;
nodeConnections?: ClusterError;
}
export const validateCluster = (fields: FormFields, isCloudEnabled: boolean): ClusterErrors => {
const { name, seeds = [], mode, proxyAddress, cloudRemoteAddress } = fields;
const { name, seeds = [], mode, proxyAddress, cloudRemoteAddress, nodeConnections } = fields;

return {
name: validateName(name),
seeds: mode === SNIFF_MODE ? validateSeeds(seeds) : null,
proxyAddress: mode === PROXY_MODE ? validateProxy(proxyAddress) : null,
cloudRemoteAddress: isCloudEnabled ? validateCloudRemoteAddress(cloudRemoteAddress) : null,
nodeConnections: mode === SNIFF_MODE ? validateNodeConnections(nodeConnections) : null,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { MAX_NODE_CONNECTIONS } from '../../../../../../common/constants';
import { validateNodeConnections } from './validate_node_connections';

describe('validateNodeConnections', () => {
test('rejects numbers larger than MaxValue', () => {
expect(validateNodeConnections(MAX_NODE_CONNECTIONS + 1)).toMatchSnapshot();
});

test('accepts numbers equal than MaxValue', () => {
expect(validateNodeConnections(MAX_NODE_CONNECTIONS)).toBe(null);
});

test('accepts numbers smaller than MaxValue', () => {
expect(validateNodeConnections(MAX_NODE_CONNECTIONS - 1)).toBe(null);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { MAX_NODE_CONNECTIONS } from '../../../../../../common/constants';

export function validateNodeConnections(connections?: number | null): null | JSX.Element {
if (connections && connections > MAX_NODE_CONNECTIONS) {
return (
<FormattedMessage
id="xpack.remoteClusters.form.errors.maxValue"
defaultMessage="This number must be equal or less than {maxValue}."
values={{
maxValue: MAX_NODE_CONNECTIONS,
}}
/>
);
}

return null;
}
2 changes: 1 addition & 1 deletion x-pack/plugins/translations/translations/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -47527,4 +47527,4 @@
"xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "Ce champ est requis.",
"xpack.watcher.watcherDescription": "Détectez les modifications survenant dans vos données en créant, gérant et monitorant des alertes."
}
}
}
2 changes: 1 addition & 1 deletion x-pack/plugins/translations/translations/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -47265,4 +47265,4 @@
"xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "フィールドを選択してください。",
"xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。"
}
}
}
2 changes: 1 addition & 1 deletion x-pack/plugins/translations/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -47318,4 +47318,4 @@
"xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "此字段必填。",
"xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。"
}
}
}