Skip to content

fix: [M3-7877] - Incorrect Linode network interface configuration being displayed#10690

Merged
hkhalil-akamai merged 12 commits intolinode:developfrom
hkhalil-akamai:M3-7877-incorrect-config-profile
Jul 29, 2024
Merged

fix: [M3-7877] - Incorrect Linode network interface configuration being displayed#10690
hkhalil-akamai merged 12 commits intolinode:developfrom
hkhalil-akamai:M3-7877-incorrect-config-profile

Conversation

@hkhalil-akamai
Copy link
Contributor

@hkhalil-akamai hkhalil-akamai commented Jul 17, 2024

Description 📝

Fixes a bug in the Linode configuration dialog causing network interfaces to be displayed incorrectly.

Changes 🔄

  • Fix race condition causing network interfaces to be displayed incorrectly
  • Add unit test to verify fix
    • Add LinodeKernelFactory to mock kernels
    • I haven't been able to replicate the bug with this unit test -- see this comment for more details

Bug pathology 🪳

The following represents my best explanation of the bug but may not be correct:

The bug is caused by a race condition between two useEffects:

  1. Updates the formik form state when config values become available

    React.useEffect(() => {
    if (open) {
    /**
    * If config is defined, we're editing. Set the state
    * to the values of the config.
    */
    if (config) {
    const devices = createStringsFromDevices(config.devices);
    /*
    If device slots are populated out of sequential order (e.g. sda and sdb are assigned
    but no others are until sdf), ascertain the last assigned slot to determine how many
    device slots to display initially.
    */
    const assignedDevices = Object.keys(devices);
    const lastAssignedDeviceSlot =
    assignedDevices[assignedDevices.length - 1];
    const positionInSequentialSlots = deviceSlots.indexOf(
    lastAssignedDeviceSlot
    );
    setDeviceCounter(positionInSequentialSlots);
    setUseCustomRoot(
    !pathsOptions.some(
    (thisOption) => thisOption.value === config?.root_device
    )
    );
    const indexOfExistingPrimaryInterface = config.interfaces.findIndex(
    (_interface) => _interface.primary === true
    );
    if (indexOfExistingPrimaryInterface !== -1) {
    setPrimaryInterfaceIndex(indexOfExistingPrimaryInterface);
    }
    resetForm({
    values: {
    comments: config.comments,
    devices,
    helpers: config.helpers,
    initrd: initrdFromConfig,
    interfaces: interfacesToState(config.interfaces),
    kernel: config.kernel,
    label: config.label,
    memory_limit: config.memory_limit,
    root_device: config.root_device,
    run_level: config.run_level,
    setMemoryLimit:
    config.memory_limit !== 0 ? 'set_limit' : 'no_limit',
    useCustomRoot: isUsingCustomRoot(config.root_device),
    virt_mode: config.virt_mode,
    },
    });
    } else {
    // Create mode; make sure loading/error states are cleared.
    resetForm({ values: defaultFieldsValues });
    setUseCustomRoot(false);
    setDeviceCounter(deviceCounterDefault);
    setPrimaryInterfaceIndex(0);
    }
    }
    }, [open, config, initrdFromConfig, resetForm, queryClient]);

  2. Modifies VPC interfaces to add IPv4 information

    React.useEffect(() => {
    if (purpose !== 'vpc') {
    return handleChange({
    ipam_address: ipamAddress,
    label,
    purpose,
    });
    }
    const changeObj = {
    ip_ranges: _additionalIPv4RangesForVPC,
    ipam_address: null,
    label: null,
    purpose,
    subnet_id: subnetId,
    vpc_id: vpcId,
    };
    /**
    * If a user checks the "Auto-assign a VPC IPv4 address" box, then we send the user inputted address, otherwise we send nothing/undefined.
    * If a user checks the "Assign a public IPv4" address box, then we send nat_1_1: 'any' to the API for auto assignment.
    */
    if (!autoAssignVPCIPv4 && autoAssignLinodeIPv4) {
    handleChange({
    ...changeObj,
    ipv4: {
    nat_1_1: 'any',
    vpc: vpcIPv4,
    },
    });
    } else if (
    (autoAssignVPCIPv4 && autoAssignLinodeIPv4) ||
    autoAssignLinodeIPv4
    ) {
    handleChange({
    ...changeObj,
    ipv4: {
    nat_1_1: 'any',
    },
    });
    } else if (autoAssignVPCIPv4 && !autoAssignLinodeIPv4) {
    handleChange({
    ...changeObj,
    });
    } else if (!autoAssignLinodeIPv4 && !autoAssignVPCIPv4) {
    handleChange({
    ...changeObj,
    ipv4: {
    vpc: vpcIPv4,
    },
    });
    }
    }, [autoAssignVPCIPv4, autoAssignLinodeIPv4, purpose]);

The race condition occurs in the following sequence:

  1. The LinodeConfigDialog is opened
  2. config is updated from undefined to the selected Linode's config -> this sets off useEffect 1 which queues the new interface values in formik
    • Due to Automatic Batching in React 18, the formik state will not actually be updated until the next render
  3. InterfaceSelect is mounted -> this sets off useEffect 2, overriding the updates from step 2

The fix:

Update useEffect 2 to return early if the interface is non-VPC

Target release date 🗓️

7/22

Preview 📷

Before After
Screen.Recording.2024-07-17.at.2.18.13.PM.mov
Screen.Recording.2024-07-17.at.2.18.36.PM.mov

How to test 🧪

Prerequisites

  • Create a Linode with a non-default network configuration
    • For example:
      eth0: VPC
      eth1: Public internet

Reproduction steps

This bug is caused by a race condition and is tricky to reproduce consistently. The following steps seem to trigger the bug reliably:

  • Navigate to the newly created Linode (from Prerequisites)
  • Navigate to the "Configurations" tab and click "Edit" on the non-default configuration to open the configuration dialog
  • Exit out of the configuration dialog and navigate to the different section of the app (e.g., Volumes, NodeBalancers, etc.)
  • Return to the Linode configuration dialog and observe the network interface fields are not correct (i.e., they show the default network interfaces)

Verification steps

  • Perform the same steps as above but observe the correct network interfaces are always dispalyed

As an Author I have considered 🤔

Check all that apply

  • 👀 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

@hkhalil-akamai hkhalil-akamai added the VPC Relating to VPC project label Jul 17, 2024
@hkhalil-akamai hkhalil-akamai self-assigned this Jul 17, 2024
@hkhalil-akamai hkhalil-akamai added Bug Fixes for regressions or bugs Ready for Review labels Jul 17, 2024
@hkhalil-akamai hkhalil-akamai changed the title fix: [M3-7877] - Fix incorrect Linode network interface configuration being displayed fix: [M3-7877] - Incorrect Linode network interface configuration being displayed Jul 17, 2024
Comment on lines -232 to -236
return handleChange({
ipam_address: ipamAddress,
label,
purpose,
});
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the actual fix.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@hana-linode can you verify this change has no unintended consequences? I wasn't able to figure out the original purpose of this.

Copy link
Contributor

Choose a reason for hiding this comment

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

@hkhalil-akamai It's been a while so I don't really remember, but I think the original purpose was to return early if the purpose wasn't vpc so that vpc properties would not be passed to the handleChange function

const renderResult = render(wrapWithTheme(ui, options));
return {
...renderResult,
rerender: (ui) => renderResult.rerender(wrapWithTheme(ui, options)),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Adding support for the rerender method

Comment on lines +150 to +173
it('should display the correct network interfaces', async () => {
const props = {
isReadOnly: false,
linodeId: 0,
onClose: vi.fn(),
};

const { findByText, rerender } = renderWithTheme(
<LinodeConfigDialog config={undefined} open={false} {...props} />
);

rerender(
<LinodeConfigDialog
config={linodeConfigFactory.build({
interfaces: [vpcInterface, publicInterface],
})}
open
{...props}
/>
);

await findByText('VPC');
await findByText('Public Internet');
});
Copy link
Contributor Author

@hkhalil-akamai hkhalil-akamai Jul 17, 2024

Choose a reason for hiding this comment

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

In theory, this test should replicate the exact sequence causing the bug. However, I haven't been able to fail this test without the fix, which I believe is due to inconsistencies between how Automatic Batching works in tests vs in the browser.

I'm still keeping this test because it may catch regressions in the future.

@hkhalil-akamai hkhalil-akamai marked this pull request as ready for review July 17, 2024 20:57
@hkhalil-akamai hkhalil-akamai requested a review from a team as a code owner July 17, 2024 20:57
@hkhalil-akamai hkhalil-akamai requested review from AzureLatte and dwiley-akamai and removed request for a team July 17, 2024 20:57
@github-actions
Copy link

github-actions bot commented Jul 18, 2024

Coverage Report:
Base Coverage: 82.46%
Current Coverage: 82.47%

Copy link
Member

@bnussman-akamai bnussman-akamai left a comment

Choose a reason for hiding this comment

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

After making no changes, clicking "Save Changes", and refreshing, the config's VPC looked different than how it was originally configured. The after refreshing again, it went back to the expected values.

Not sure if this is related. I haven't been able to easily reproduce.

Screen.Recording.2024-07-22.at.9.58.27.AM.mov

@hkhalil-akamai
Copy link
Contributor Author

After making no changes, clicking "Save Changes", and refreshing, the config's VPC looked different than how it was originally configured. The after refreshing again, it went back to the expected values.

I have been unable to reproduce this. There is a lot of complicated logic in the LinodeConfigDialog and InterfaceSelect components that needs to be cleaned up but I don't know if it's being caused by this change. Are you able to reproduce it in prod?

Copy link
Member

@bnussman-akamai bnussman-akamai left a comment

Choose a reason for hiding this comment

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

I wasn't able to reproduce the issue I showed above, so I think we're good

@hana-akamai
Copy link
Contributor

hana-akamai commented Jul 23, 2024

I was able to reproduce the bug @bnussman-akamai saw on this branch on a fresh Linode and new VPC created during the flow. I was not able to reproduce in prod 🤔

Copy link
Contributor

@dwiley-akamai dwiley-akamai left a comment

Choose a reason for hiding this comment

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

Code review ✅
Observing correct network interfaces ✅

I was not able to reproduce the issue Banks observed, and I didn't observe any adverse impacts from the changes in InterfaceSelect.tsx

@hkhalil-akamai
Copy link
Contributor Author

hkhalil-akamai commented Jul 23, 2024

@bnussman-akamai @hana-linode @dwiley-akamai

I was able to trace back the issue to the same useEffect in InterfaceSelect.tsx. I believe I have solved the issue by removing the useEffect and rewriting the onChange handlers to modify the interface directly (545f6db).

The following steps seem to reliably reproduce the issue (before the fix):

  1. Create a new Linode with the eth0 interface set to VPC.

    • Deselect "Auto-assign a VPC IPv4 address for this Linode in the VPC" and manually set an IPv4 address such as "10.0.0.2"
    • Select "Assign a public IPv4 address for this Linode"
    • For example, your configuration could look like this:
  2. Navigate to the Linode details page -> Configuration tab and edit the configuration

  3. Verify the VPC interface appears as expected (i.e., with the first checkbox deselected and the second checkbox selected) -- you may need to refresh the page to ensure the interface appears as expected.

  4. Navigate to a different page in CM (e.g., NodeBalancers, Volumes, etc.)

  5. Return to the Linode details page -> Configuration tab -> Edit configuration and observe the VPC interface has been reset (the first checkbox is selected and the second checkbox is deselected)

@hana-akamai
Copy link
Contributor

So it seems like editing the config doesn't update the UI now 💀 . See the checkbox still being checked on this branch vs unchecked on prod

Screen.Recording.2024-07-23.at.3.06.47.PM.mov

@bnussman-akamai
Copy link
Member

I haven't been able to replicate the first bug I brought up, which is good ✅

I am seeing the form reset to a weird state when I try to remove a VPC IP range, which does not happen in production.

Screen.Recording.2024-07-23.at.3.08.54.PM.mov

Copy link
Member

@bnussman-akamai bnussman-akamai left a comment

Choose a reason for hiding this comment

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

No more bugs found from my testing 🎉

Copy link
Contributor

@hana-akamai hana-akamai left a comment

Choose a reason for hiding this comment

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

No more bugs found as well 🎉

@bnussman-akamai bnussman-akamai added Approved Multiple approvals and ready to merge! and removed Ready for Review labels Jul 29, 2024
@hkhalil-akamai hkhalil-akamai merged commit 7ee33e0 into linode:develop Jul 29, 2024
@hkhalil-akamai hkhalil-akamai deleted the M3-7877-incorrect-config-profile branch July 29, 2024 21:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Approved Multiple approvals and ready to merge! Bug Fixes for regressions or bugs VPC Relating to VPC project

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

5 participants