Skip to content

Closes #23654: Add best practices guide for modeling servers - #23668

Open
jnovinger wants to merge 2 commits into
mainfrom
23654-modeling-servers-doc
Open

jnovinger wants to merge 2 commits into
mainfrom
23654-modeling-servers-doc

Conversation

@jnovinger

Copy link
Copy Markdown
Member

Closes: #23654

Adds a best practices page for modeling physical servers with modules, module bays and module type profiles, alongside the existing pluggable transceivers guide. It covers when to nest modules and when to stay flat, an eight-step setup from profiles through bulk import, and moving and removing modules. Two worked examples follow the same shape (device type, module types, installation, then a day-2 task): a Dell PowerEdge R760 ending with replacing a failed drive, and a nested GPU platform (Meta Grand Teton) ending with moving an accelerator tray to a spare chassis.

The examples were checked against the vendor documentation, and the behavior the page describes was checked against a test instance (child bays created on install, {module} resolution in bay and component names, bay type compatibility, the two-pass bulk import, cascade on move and delete, disabled bays rejecting installs).

@jnovinger

Copy link
Copy Markdown
Member Author

@claude /review Only report on issues of potential concern; we don't need commentary on anything that doesn't require further consideration.

@jnovinger
jnovinger marked this pull request as draft November 10, 2026 22:52
@github-actions

github-actions Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @jnovinger's task in 8m 40s —— View job


Review: docs-only PR (docs/best-practices/modeling-servers.md, mkdocs.yml)

  • Read the diff
  • Verify documented behavior against the codebase
  • Post findings

I checked every behavioral claim in the page against the 4.7 source rather than just reading for prose. Almost all of it holds up; below are the two things I think are worth another look, followed by what I verified so you don't have to re-check it.


Findings

1. The two-pass bulk import looks stricter than NetBox actually requires — docs/best-practices/modeling-servers.md:197 (and the parallel text at line 283 for Grand Teton)

The drive bays exist only once the backplane has been installed … so install those first. With bulk import, import the modules in two passes:

Installing the parents first is correct, but a single CSV with the parent rows ordered ahead of the child rows also works. BulkImportView._process_import_records() (netbox/netbox/views/generic/bulk_views.py:595-655) instantiates, validates and saves each record one at a time in file order, and ModuleImportForm.__init__ (netbox/dcim/forms/bulk_import.py:853-859) resolves module_bay against the device at form-construction time — so bays created by row 1 are already persisted and visible to row 3. Nothing pre-validates all rows up front (BulkImportForm.clean() only parses).

Suggest softening to something like "order the parent modules ahead of their contents, or import in two passes" so readers don't think an ordered single file is rejected. Fix this →

2. Two examples assign a bay type on only one side, which silently turns the constraint off — docs/best-practices/modeling-servers.md:189 and :243-245

Step 2 teaches that compatibility is enforced when both the bay and the module type declare bay types (correct — Module.is_bay_compatible, netbox/dcim/models/modules.py:428-441). But:

  • BOSS-N1 M.2 NVMe SSD is given bay type M.2 Slot (line 189), while line 193 defines the BOSS-N1's M.2-1/M.2-2 bays with names and positions only. M.2 Slot appears nowhere else on the page, so a reader building the example literally gets no enforcement on those bays and has no instruction telling them where M.2 Slot is assigned.
  • In Grand Teton, the chassis bays take GT Tray Slot (lines 243-245), but the "Tray Module Types" table's Bay Type column describes the trays' nested bays — the tray module types themselves are never assigned GT Tray Slot, so a CPU tray could be installed into GPU-TRAY.

Low severity, but since these are the page's showcase examples of bay types it's worth closing the loop on both. Fix this →


Verified as correct (no action needed)

Listing these because they're the claims most likely to rot, and they all check out against 4.7.5:

Claim Verified against
Default profiles + their attribute sets (CPU/Memory/Hard disk/GPU/Expansion card/Power supply/Fan) dcim/migrations/initial_data/module_type_profiles/*.json — all seven tables match exactly
Expansion card profile missing on installs that reached v4.3 before v4.3.4 docs/release-notes/version-4.3.md:85 (#19571, v4.3.4), added to the existing 0206 migration
Memory/Hard disk/Power supply schemas carry defaults the form pre-fills JSONSchemaProperty.to_form_field() sets initial=self.default
Bay types enforce "share at least one type"; unset on either side = unconstrained Module.is_bay_compatible
{module} resolves to the position of the containing bay; single token → leaf bay in a nested tree dcim/utils.py:76-109, resolve_module_placeholder()
Install into a bay with no position is rejected when templates use {module} same, raises → AbortRequest
{module}-Bay[1-8] range expansion works alongside the token ALPHANUMERIC_EXPANSION_PATTERN splits on the brackets only
Bay types are copied onto bays instantiated from templates, for both device creation and module install devices.py:1126-1129, modules.py:600-604
Device-list Add Components > Module Bays has no bay-type field (bulk edit does) ModuleBayBulkCreateForm vs. ModuleBayBulkEditForm — and notably ModuleBayTemplateCreateForm does include it, so step 6's instruction is right too
Single position value replicated across all bays in that form ComponentCreateForm.clean()
Module import keys on device name, module_bay name within device, module_type model ModuleImportForm
Model name collision risk across manufacturers ModuleType unique constraint is (manufacturer, model)
Disabled bay rejects installs but doesn't evict an existing module Module.clean(); ModuleBay.clean() has no such guard
Delete cascades to nested modules; module always requires a bay ModularComponentModel.module CASCADE, Module.module_bay non-nullable
Cross-device move conditions, Failed status, serial in global search, ?profile=…&attr_…= filters docs/models/dcim/module.md, ModuleStatusChoices, ModuleIndex, AttributeFiltersMixin
Child device types must be 0U DeviceType.clean()

Links (modulebaytype.md, modulebay.md, devicebay.md, etc.), the three in-page anchors, mermaid fence config, no-highlight usage, and the nav placement in mkdocs.yml are all fine. Structure mirrors the transceivers page, and nothing here contradicts it.
· 23654-modeling-servers-doc

@jnovinger

Copy link
Copy Markdown
Member Author

1. The two-pass bulk import looks stricter than NetBox actually requires — docs/best-practices/modeling-servers.md:197 (and the parallel text at line 283 for Grand Teton)

Agreed, fixed in 6dafba2

2. Two examples assign a bay type on only one side, which silently turns the constraint off — docs/best-practices/modeling-servers.md:189 and :243-245

Fixed in 6dafba2: the BOSS-N1's M.2-1/M.2-2 bays now take M.2 Slot, and the tray module types take GT Tray Slot. All three trays share that one type on purpose to keep the example small, so a CPU tray still fits GPU-TRAY. A reader who wants NetBox to tell the trays apart can give each its own type.

@jnovinger
jnovinger marked this pull request as ready for review November 10, 2026 23:30
@jnovinger
jnovinger requested review from a team and jeremystretch and removed request for a team November 10, 2026 23:30

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Document best practices for modeling servers

1 participant