Skip to content

upcoming: [M3-9424] - Review nodebalancers validation schemas #11910

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 11 commits into
base: develop
Choose a base branch
from

Conversation

harsh-akamai
Copy link
Contributor

@harsh-akamai harsh-akamai commented Mar 24, 2025

Description 📝

Update validation schemas for the changes in POST endpoints in /v4/nodebalancers (& /v4beta/nodebalancers) for NB-VPC Integration

How to test 🧪

Verification steps

  • Verify the changes match the APISpec linked in the parent ticket
Author Checklists

As an Author, to speed up the review process, I considered 🤔

👀 Doing a self review
❔ Our contribution guidelines
🤏 Splitting feature into small PRs
➕ Adding a changeset
🧪 Providing/improving test coverage
🔐 Removing all sensitive information from the code and PR description
🚩 Using a feature flag to protect the release
👣 Providing comprehensive reproduction steps
📑 Providing or updating our documentation
🕛 Scheduling a pair reviewing session
📱 Providing mobile support
♿ Providing accessibility support


  • I have read and considered all applicable items listed above.

As an Author, before moving this PR from Draft to Open, I confirmed ✅

  • All unit tests are passing
  • TypeScript compilation succeeded without errors
  • Code passes all linting rules

@harsh-akamai harsh-akamai self-assigned this Mar 24, 2025
@harsh-akamai harsh-akamai marked this pull request as ready for review March 24, 2025 12:51
@harsh-akamai harsh-akamai requested a review from a team as a code owner March 24, 2025 12:51
@harsh-akamai harsh-akamai requested review from cpathipa and coliu-akamai and removed request for a team March 24, 2025 12:51
@harsh-akamai harsh-akamai added the NB-VPC Relating to NodeBalancer-VPC integration label Mar 24, 2025
@@ -97,6 +97,7 @@ export const vpcsValidateIP = ({
}

if (isIPv6) {
// @todo update the IPv6 prefix if required for NB-VPC integration
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// @todo update the IPv6 prefix if required for NB-VPC integration
// @TODO NB-VPC: update the IPv6 prefix if required for NB-VPC integration

ipv6_range: string() //TBD
.notRequired()
.nullable()
.matches(PRIVATE_IP_REGEX, 'Must be a valid private IPv6 address.')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IPv6 has a different format than IPv4, so an IPv6-specific regex will need to be used and we should revert the name change to the existing regex variable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected!

.required('Subnet ID is required.'),
ipv4_range: string()
.typeError('IPv4 address is required.')
.required('IPv4 address is required.')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the case? I would imagine it could have either an IPv4 range or IPv6 range, or both, but not neither. Assuming what I described is accurate, we could use similar logic as what was done for createSubnetSchemaWithIPv6 in vpcs.schema.ts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds right. I've made changes to adapt to the logic present in createSubnetSchemaWithIPv6

@dwiley-akamai
Copy link
Contributor

Can we also get a validation changeset?

…dpoints in /v4/nodebalancers (& /v4beta/nodebalancers) for NB-VPC Integration

const PORT_WARNING = 'Port must be between 1 and 65535.';
const LABEL_WARNING = 'Label must be between 3 and 32 characters.';

export const PRIVATE_IPv4_REGEX = /^10\.|^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[0-1]\.|^192\.168\.|^fd/;
export const PRIVATE_IPv6_REGEX = /^(fc|fd)\./;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this regex will match valid IPv6 addresses. We should test the regex expressions with a tool like regex101.com to ensure it works as intended. We'll also want it to account for the mask since it's an IPv6 range

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sample IPv6 range fails the regex pattern:

Screenshot 2025-04-10 at 4 38 18 PM

It may be that a comprehensive regex for IPv6 may be challenging or too complex for our purposes, but if that is the case we should note in a comment that we're only testing the beginning letters of it rather than the entire IP.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted. I've made changes to the regex and I've also added a comment. Thanks for catching this 🙌

then: (schema) =>
schema
.required(IP_EITHER_BOTH_NOT_NEITHER)
.matches(PRIVATE_IPv4_REGEX, 'Must be a valid private IPv4 address.')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's store the 'Must be a valid private IPv4 address.' string in a constant since we use it several times

Comment on lines 264 to 332
const createNodeBalancerVPCsSchema = object({
subnet_id: number()
.typeError('Subnet ID must be a number.')
.required('Subnet ID is required.'),
ipv4_range: string().when('ipv6_range', {
is: (value: unknown) =>
value === '' || value === null || value === undefined,
then: (schema) =>
schema
.required(IP_EITHER_BOTH_NOT_NEITHER)
.matches(PRIVATE_IPv4_REGEX, 'Must be a valid private IPv4 address.')
.test({
name: 'IPv4 CIDR format',
message: 'The IPv4 range must be in CIDR format.',
test: (value) =>
vpcsValidateIP({
value,
shouldHaveIPMask: true,
mustBeIPMask: false,
}),
}),
otherwise: (schema) =>
lazy((value: string | undefined) => {
switch (typeof value) {
case 'undefined':
return schema.notRequired().nullable();

case 'string':
return schema
.notRequired()
.matches(
PRIVATE_IPv4_REGEX,
'Must be a valid private IPv4 address.'
)
.test({
name: 'IPv4 CIDR format',
message: 'The IPv4 range must be in CIDR format.',
test: (value) =>
vpcsValidateIP({
value,
shouldHaveIPMask: true,
mustBeIPMask: false,
}),
});

default:
return schema.notRequired().nullable();
}
}),
}),
ipv6_range: string().when('ipv6_range', {
is: (value: unknown) =>
value === '' || value === null || value === undefined,
then: (schema) =>
schema
.required(IP_EITHER_BOTH_NOT_NEITHER)
.matches(PRIVATE_IPv6_REGEX, 'Must be a valid private IPv6 address.')
.test({
name: 'valid-ipv6-range',
message: 'Must be a valid IPv6 range, e.g. 2001:db8:abcd:0012::0/64.',
test: (value) =>
vpcsValidateIP({
value,
shouldHaveIPMask: true,
mustBeIPMask: false,
}),
}),
}),
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now, Cloud Manager doesn't load and we get the following console error:

Screenshot 2025-04-04 at 1 20 29 PM

Suggested change
const createNodeBalancerVPCsSchema = object({
subnet_id: number()
.typeError('Subnet ID must be a number.')
.required('Subnet ID is required.'),
ipv4_range: string().when('ipv6_range', {
is: (value: unknown) =>
value === '' || value === null || value === undefined,
then: (schema) =>
schema
.required(IP_EITHER_BOTH_NOT_NEITHER)
.matches(PRIVATE_IPv4_REGEX, 'Must be a valid private IPv4 address.')
.test({
name: 'IPv4 CIDR format',
message: 'The IPv4 range must be in CIDR format.',
test: (value) =>
vpcsValidateIP({
value,
shouldHaveIPMask: true,
mustBeIPMask: false,
}),
}),
otherwise: (schema) =>
lazy((value: string | undefined) => {
switch (typeof value) {
case 'undefined':
return schema.notRequired().nullable();
case 'string':
return schema
.notRequired()
.matches(
PRIVATE_IPv4_REGEX,
'Must be a valid private IPv4 address.'
)
.test({
name: 'IPv4 CIDR format',
message: 'The IPv4 range must be in CIDR format.',
test: (value) =>
vpcsValidateIP({
value,
shouldHaveIPMask: true,
mustBeIPMask: false,
}),
});
default:
return schema.notRequired().nullable();
}
}),
}),
ipv6_range: string().when('ipv6_range', {
is: (value: unknown) =>
value === '' || value === null || value === undefined,
then: (schema) =>
schema
.required(IP_EITHER_BOTH_NOT_NEITHER)
.matches(PRIVATE_IPv6_REGEX, 'Must be a valid private IPv6 address.')
.test({
name: 'valid-ipv6-range',
message: 'Must be a valid IPv6 range, e.g. 2001:db8:abcd:0012::0/64.',
test: (value) =>
vpcsValidateIP({
value,
shouldHaveIPMask: true,
mustBeIPMask: false,
}),
}),
}),
});
const createNodeBalancerVPCsSchema = object().shape(
{
subnet_id: number()
.typeError('Subnet ID must be a number.')
.required('Subnet ID is required.'),
ipv4_range: string().when('ipv6_range', {
is: (value: unknown) =>
value === '' || value === null || value === undefined,
then: (schema) =>
schema
.required(IP_EITHER_BOTH_NOT_NEITHER)
.matches(PRIVATE_IPv4_REGEX, 'Must be a valid private IPv4 address.')
.test({
name: 'IPv4 CIDR format',
message: 'The IPv4 range must be in CIDR format.',
test: (value) =>
vpcsValidateIP({
value,
shouldHaveIPMask: true,
mustBeIPMask: false,
}),
}),
otherwise: (schema) =>
lazy((value: string | undefined) => {
switch (typeof value) {
case 'undefined':
return schema.notRequired().nullable();
case 'string':
return schema
.notRequired()
.matches(
PRIVATE_IPv4_REGEX,
'Must be a valid private IPv4 address.'
)
.test({
name: 'IPv4 CIDR format',
message: 'The IPv4 range must be in CIDR format.',
test: (value) =>
vpcsValidateIP({
value,
shouldHaveIPMask: true,
mustBeIPMask: false,
}),
});
default:
return schema.notRequired().nullable();
}
}),
}),
ipv6_range: string().when('ipv4_range', {
is: (value: unknown) =>
value === '' || value === null || value === undefined,
then: (schema) =>
schema
.required(IP_EITHER_BOTH_NOT_NEITHER)
.matches(PRIVATE_IPv6_REGEX, 'Must be a valid private IPv6 address.')
.test({
name: 'valid-ipv6-range',
message:
'Must be a valid IPv6 range, e.g. 2001:db8:abcd:0012::0/64.',
test: (value) =>
vpcsValidateIP({
value,
shouldHaveIPMask: true,
mustBeIPMask: false,
}),
}),
}),
},
[
['ipv4_range', 'ipv6_range'],
['ipv6_range', 'ipv4_range'],
]
);

The array at the end prevents the cyclic dependency error.

Copy link
Contributor

@cpathipa cpathipa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@harsh-akamai LGTM! Can you resolve the ESLint warning.

@@ -1,9 +1,12 @@
import { array, boolean, mixed, number, object, string } from 'yup';
import { array, boolean, lazy, mixed, number, object, string } from 'yup';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@harsh-akamai Can you look into this ESLint warnings.

@harsh-akamai harsh-akamai added the Add'tl Approval Needed Waiting on another approval! label Apr 14, 2025
@harsh-akamai harsh-akamai requested a review from a team as a code owner April 16, 2025 06:43
@harsh-akamai harsh-akamai requested review from dmcintyr-akamai and removed request for a team April 16, 2025 06:43
@harsh-akamai harsh-akamai force-pushed the M3-9424-review-nb-vpc-validation-schema branch from 211feb4 to 291f89d Compare April 16, 2025 06:48
@harsh-akamai harsh-akamai removed the Add'tl Approval Needed Waiting on another approval! label Apr 16, 2025
.test(
'unique subnet IDs',
'Subnet IDs must be unique.',
function (value?: any[] | null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to include a type annotation as yup will infer one for you!

Suggested change
function (value?: any[] | null) {
function (value) {

@linode-gh-bot
Copy link
Collaborator

Cloud Manager UI test results

🔺 1 failing test on test run #11 ↗︎

❌ Failing✅ Passing↪️ Skipped🕐 Duration
1 Failing539 Passing4 Skipped104m 7s

Details

Failing Tests
SpecTest
bucket-details-multicluster.spec.tsCloud Manager Cypress Tests→Object Storage Multicluster Bucket Details Tabs→Properties tab without required capabilities » Object Storage Multicluster Bucket Details Tabs→Properties tab without required capabilities

Troubleshooting

Use this command to re-run the failing tests:

pnpm cy:run -s "cypress/e2e/core/objectStorageMulticluster/bucket-details-multicluster.spec.ts"

@harsh-akamai harsh-akamai requested a review from cpathipa April 17, 2025 11:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NB-VPC Relating to NodeBalancer-VPC integration
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants