Skip to content

[Nexthop][fboss2-dev] Add fboss2-dev delete vlan subcommand - #1418

Open
vybhav-nexthop wants to merge 4 commits into
facebook:mainfrom
nexthop-ai:delete-vlan-subtree
Open

[Nexthop][fboss2-dev] Add fboss2-dev delete vlan subcommand#1418
vybhav-nexthop wants to merge 4 commits into
facebook:mainfrom
nexthop-ai:delete-vlan-subtree

Conversation

@vybhav-nexthop

@vybhav-nexthop vybhav-nexthop commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Pre-submission checklist

  • I've ran the linters locally and fixed lint errors related to the files I modified in this PR. You can install the linters by running pip install -r requirements-dev.txt && pre-commit install
  • pre-commit run

Summary

What: Adds fboss2-dev delete vlan <id> — removes a VLAN (the whole sw.vlans[] entry) from the config session.

Why: Delete-side counterpart for config vlan; created VLANs could not be removed.

How: VlanManager::deleteVlan pairs with the existing createVlan. It cascade-removes the child objects that can't outlive the VLAN:

  • the interface(s) bound to it (Interface.vlanID) — both the barebone one createVlan generates and a routed SVI carrying IP addresses; an interface's vlanID must reference an existing VLAN, so it cannot outlive it
  • switchport membership rows naming it (VlanPort.vlanID) — same effect as config interface <port> switchport trunk allowed vlan remove <id> on every member port
  • static MAC entries scoped to it (StaticMacEntry.vlanID)

The delete is refused (with a message naming the referrers) only where cascading would orphan a reference or change what a sibling object means. Each of those has an existing CLI path to clear it first:

Refusal Prerequisite to clear it
it is the global default VLAN (SwitchConfig.defaultVlan) config vlan default <other-id>
a port lists it as its untagged ingress VLAN (Port.ingressVlan) — a required scalar whose only fallback value, 0, means routed port config interface <port> switchport access vlan <other-id>

Known/accepted edge: an ACL redirect action referencing the deleted interface as a nexthop (RedirectNextHop.intfID) is not checked — such an ACL stops resolving and the agent disables it at config apply. That reference can only come from configs authored outside fboss2 (the CLI sets redirect nexthops by IP only), and repairing the ACL is the operator's responsibility.

Saves with the default HITLESS action level, matching the config vlan subcommands.

Also: stable_sort for the command trees. The CLI registers "config vlan" twice on purpose:

  • config vlan <vlan-id> <attr...> — this entry owns the <vlan-id> positional
  • config vlan default <value> — separate entry so its value registers at a different arg depth

The root command list is sorted by name only (RootCommand::operator< ignores the verb), and std::sort gives no ordering guarantee for equal names. Adding delete vlan — a third entry named "vlan" — flipped the order of the two "config vlan" entries. What then happened at registration:

  1. The default entry is processed first → it creates the vlan CLI node. It has no <vlan-id> positional, so none is registered.
  2. The <vlan-id> entry is processed second → the node already exists, so it takes the merge path. The merge copies subcommands (port, static-mac) but never runs the entry's arg registrar — positionals are registered only when an entry creates the node. The <vlan-id> positional is silently dropped.

End state: config vlan default 300 works, but config vlan 2 static-mac add ... fails with "The following argument was not expected: 2" — every config vlan <vlan-id> command broke (caught by the vlan integration tests in CI). stable_sort keeps equal-named entries in source order, so the <vlan-id>-owning entry always creates the node.

Test Plan

Unit tests (8 new; VlanManager create-side tests still green):

[==========] 8 tests from CmdDeleteVlanTestFixture ran.
[  PASSED  ] 8 tests.

Covered: not-found, refuse default / ingress-port, happy-path removal of VLAN + barebone interface + cascade static-MAC + cascade membership, SVI-with-IP deletes via interface cascade, member-only VLAN deletes via cascade, and two handler (queryClient) cases.

The happy-path test seeds a trunk port in both the target VLAN and the default VLAN, so it asserts the membership cascade is scoped: the target's row goes, the default VLAN's row for the same port survives.

The membership and SVI cascades are covered by unit tests only — they have not been re-run on hardware. The integration test exercises neither switchport membership nor an IP-bearing SVI, so there is nothing there for them to catch either way. The device results below predate them.

Integration tests — executed on an Nexthop device, both PASSED:

[       OK ] DeleteVlanTest.CreateThenDeleteVlan (64 ms)
[       OK ] DeleteVlanTest.CommitDeleteExistingVlan (95 ms)
[  PASSED  ] 2 tests.
  • CommitDeleteExistingVlan: deletes a committed, unreferenced VLAN, commits, and verifies it disappears from the running config. The commit was hitless — both agents kept NRestarts unchanged and stayed active. Skips when the running config has no deletable VLAN.
$ fboss2-dev delete vlan 10
Deleted VLAN 10
$ fboss2-dev config session commit
Config session committed successfully as bca47d3 and config reloaded for fboss_sw_agent.

# post-commit: NRestarts=0 ActiveState=active (both agents); committed vlans -> [4094]
$ fboss2-dev delete vlan 4094
Thrift call failed: 'Cannot delete VLAN 4094: it is the global default VLAN'

No issues found above confidence threshold (pre-publication automated review).

@vybhav-nexthop
vybhav-nexthop requested review from a team as code owners July 27, 2026 06:00
@meta-cla meta-cla Bot added the CLA Signed label Jul 27, 2026
Delete-side counterpart for config vlan:

  delete vlan <id>

removes the whole sw.vlans[] entry. VlanManager::deleteVlan pairs with
the existing createVlan: it also drops the barebone L3 interface that
createVlan generates for every VLAN (matching vlanID, no IP addresses)
and cascade-removes static MAC entries scoped to the VLAN, since those
are child objects that cannot outlive it.

To avoid silently orphaning references, the delete is refused (with a
message naming the referrers) when the VLAN is still in use:
  - it is the global default VLAN (SwitchConfig.defaultVlan)
  - a port lists it as its untagged ingress VLAN (Port.ingressVlan)
  - a port is a member of it (VlanPort.vlanID)
  - it backs a routed SVI, i.e. an interface with IP addresses

Saves with the default HITLESS action level, matching the config vlan
subcommands. Verified on a DUT that committing the delete reloads the
config hitlessly (no agent restart, no crash).
vybhav-nexthop and others added 2 commits July 27, 2026 09:25
Always program a barebone unreferenced VLAN, assert present, delete,
assert gone. Drop the session-only IT.

Co-authored-by: Cursor <cursoragent@cursor.com>
Switchport membership rows carry no configuration the user would have to
re-supply, and dropping one leaves the port valid in its remaining VLANs, so
refusing the delete only forced a manual `switchport trunk allowed vlan
remove` on every member port first. Cascade them like the static MAC entries
already are.

The other refusals stay. defaultVlan and Port.ingressVlan are required
scalars whose only fallback value (0) means routed port, so clearing them
would change what the port is. An SVI with IP addresses carries user L3
config. Access ports keep their protection either way: the ingressVlan
refusal runs before the cascade.
@meta-codesync

meta-codesync Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

@joseph5wu has imported this pull request. If you are a Meta employee, you can view this in D113981193.

An interface's vlanID must reference an existing VLAN, so the interface
cannot outlive it. Refusing when the SVI carried IP addresses only added
friction: the user deleting the VLAN has already opted to drop its L3
interface. Remove the refusal and cascade-delete the interface regardless
of configured addresses.
@facebook-github-tools

Copy link
Copy Markdown

@vybhav-nexthop has updated the pull request. You must reimport the pull request before landing.

1 similar comment
@facebook-github-tools

Copy link
Copy Markdown

@vybhav-nexthop has updated the pull request. You must reimport the pull request before landing.

@facebook-github-tools

Copy link
Copy Markdown

@vybhav-nexthop has updated the pull request. You must reimport the pull request before landing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant