FreeNAS/freenas 2bfa458 — src/middlewared/middlewared/alembic/versions/28.0 2026-11-09_12-00_tnc_status_detail.py, src/middlewared/middlewared/plugins/truenas_connect utils.py internal.py

NAS-144998: Report the TNC rejection reason in status_reason
DeltaFile
+133-0src/middlewared/middlewared/pytest/unit/plugins/test_truenas_connect_status_detail.py
+27-0src/middlewared/middlewared/alembic/versions/28.0/2026-11-09_12-00_tnc_status_detail.py
+8-3src/middlewared/middlewared/plugins/truenas_connect/finalize_registration.py
+2-1src/middlewared/middlewared/plugins/truenas_connect/config.py
+1-1src/middlewared/middlewared/plugins/truenas_connect/internal.py
+1-0src/middlewared/middlewared/plugins/truenas_connect/utils.py
+172-56 files

FreeNAS/freenas a6c3eed — src/middlewared/middlewared/plugins/directoryservices_ datastore.py, src/middlewared/middlewared/utils/directoryservices credential.py

NAS-142154 / 27.1.0 / Reject new overlaps between Active Directory idmap ranges (by anodos325) (#20162)

The directory services refactor (368cc8cf63) removed the idmap plugin's
range validation and kept only a check between trusted domains. A
trusted domain could then share IDs with the primary domain or BUILTIN.

- Check BUILTIN, the primary domain and enabled trusted domains in
directoryservices.update.
- Leave overlaps already in the saved configuration alone, so that
existing setups can still be disabled, enabled and saved.

Original PR: https://github.com/truenas/middleware/pull/20148

---------

Co-authored-by: Andrew Walker <andrew.walker at truenas.com>
DeltaFile
+521-0tests/unit/test_directoryservices_idmap_ranges.py
+81-0src/middlewared/middlewared/plugins/directoryservices_/datastore.py
+66-0tests/directory_services/test_activedirectory_idmap.py
+11-7src/middlewared/middlewared/utils/directoryservices/credential.py
+679-74 files

FreeNAS/freenas ab2f0c9 — src/middlewared/middlewared/plugins sysdataset.py, src/middlewared/middlewared/plugins/container lifecycle.py dataset.py

Pin dedup off on internal datasets

## Problem
Dedup can be enabled on the pool root dataset, either at pool creation or later, and the internal `.system` and `.truenas_containers` trees inherit it, so system dataset and container writes go through the dedup table. ix-apps already pins `dedup=off`, but its drift check compares raw values only, so an ix-apps tree created before that pin and inheriting `off` is never pinned locally and would follow the root once it is switched on.

## Solution
Rather than refusing dedup on the root, which is a supported pool.create workflow, the internal trees now carry a local `dedup=off`:
- **System dataset**: `.system` is created with `dedup=off` (children inherit it), and `setup_datasets` re-pins dedup whenever its source is not local, so existing systems get pinned on the next boot/import/failover reconcile.
- **Containers**: `.truenas_containers` is created with `dedup=off`, and an existing tree is re-pinned when datasets are ensured (create/pull/migrate) and on container start, which also covers boot autostart and failover.
- **ix-apps**: the drift check also fires when dedup is not set locally.

Blocks that were already written deduplicated stay in the dedup table until rewritten; only new writes are affected.
DeltaFile
+23-1src/middlewared/middlewared/plugins/container/dataset.py
+10-2src/middlewared/middlewared/plugins/sysdataset.py
+5-2src/middlewared/middlewared/plugins/docker/state_setup.py
+2-1src/middlewared/middlewared/plugins/container/lifecycle.py
+2-0src/middlewared/middlewared/plugins/system_dataset/hierarchy.py
+42-65 files

FreeNAS/freenas 782eda5 — src/middlewared/middlewared/plugins/container image.py crud.py

Harden container image pull

## Problem
Pulling a container image leaked raw `requests` errors, had no timeout on the tarball download so a stalled stream hung forever, and never checked the published sha256. Aborting `container.create` was ignored by the pull it was waiting on, and two concurrent pulls of the same uncached image could destroy each other's in-progress dataset through the orphan cleanup.

## Solution
- **Error wrapping**: registry index failures are raised as a readable `CallError` instead of a raw `requests` exception.
- **Timeout**: the tarball download uses `INTERNET_TIMEOUT`, so a stall fails the pull and the image dataset is destroyed.
- **Checksum**: the download is hashed as it streams and compared against the registry's sha256 before anything is extracted.
- **Registry lookup first**: the image/version lookup happens before `ensure_datasets`, the orphan destroy and the dataset create, so an unknown image or unreachable registry never touches a dataset. Cached images still return early without network access.
- **Abort**: the pull checks for abort in the download and extract loops. `container.create` aborts its pull job when it is cancelled, and raises `JobCancelledException` if the pull was aborted on its own.
- **Per-image lock**: `container.image.pull` holds a lock keyed on pool, name and version with `lock_queue_size=None`, so each create waits for its own pull job and a waiter after a successful pull hits the cached snapshot.
DeltaFile
+43-24src/middlewared/middlewared/plugins/container/query_pull_images.py
+10-3src/middlewared/middlewared/plugins/container/crud.py
+4-1src/middlewared/middlewared/plugins/container/image.py
+57-283 files

FreeNAS/freenas 73e9192 — src/middlewared/middlewared/plugins/container __init__.py lifecycle.py, src/middlewared/middlewared/plugins/vm __init__.py lifecycle.py

Fail graceful VM and container stop when the guest keeps running

## Problem
A graceful `vm.stop` / `container.stop` (no `force`, no `force_after_timeout`, the API default) waited `shutdown_timeout` and then reported SUCCESS even when the guest ignored the shutdown request and was still running. The UI showed the guest as stopped and a graceful container restart then failed on start with "already started". A suspended VM was also left suspended with a successful job, even with `force_after_timeout`, because the post-stop check only looked for `RUNNING`.

## Solution
After the shutdown request, both stop paths check the state again. If the guest has not stopped, it is powered off when `force_after_timeout` is set; otherwise the job fails with an ETIMEDOUT `CallError` saying it did not stop within the timeout. A suspended VM is resumed before the shutdown request so the guest can act on it, and library errors (e.g. stopping a guest that is not running) are raised as `CallError` like start already does. The docstrings describe the new behaviour and the container stop test now expects the graceful stop to fail.
DeltaFile
+20-9src/middlewared/middlewared/plugins/vm/lifecycle.py
+17-9src/middlewared/middlewared/plugins/container/lifecycle.py
+4-4tests/api2/test_container.py
+7-1src/middlewared/middlewared/plugins/container/__init__.py
+3-4src/middlewared/middlewared/plugins/vm/__init__.py
+51-275 files

FreeNAS/freenas 5f0d6e7 — src/middlewared/middlewared/plugins/container migrate.py crud.py, src/middlewared/middlewared/plugins/vm crud.py

Validate container configuration values when they are saved

## Problem
Several container settings passed create and update but only failed at start, often as a raw IndexError or ValueError: an unparseable or empty init, a cpuset with whitespace or CPUs that are not online, and a memory limit above host RAM. A missing initdir made start report success for a container that exited immediately.

## Solution
- **Save time**: init must parse to at least one token; cpuset must be well formed and online, and memory within host RAM. The cpuset and memory checks only run when the value changes, so a host that has since lost CPUs or memory does not block unrelated edits.
- **Start time**: initdir must resolve to a directory inside the container root.
- **Migration**: an incus CPU count, or a CPU set or memory limit this host can't satisfy, is dropped with a job log line instead of failing the container.
- An invalid pinned VM cpuset is a validation error instead of an unhandled ValueError, and autostart failures are logged with a traceback. Accepting `0-0` relies on the matching truenas_pylibvirt parser change.
DeltaFile
+44-3src/middlewared/middlewared/plugins/container/lifecycle.py
+34-11src/middlewared/middlewared/plugins/container/crud.py
+45-0src/middlewared/middlewared/pytest/unit/plugins/container/test_config_values.py
+22-2src/middlewared/middlewared/plugins/container/migrate.py
+12-6src/middlewared/middlewared/plugins/vm/crud.py
+6-0src/middlewared/middlewared/utils/cpu/info.py
+163-221 files not shown
+165-237 files

FreeNAS/freenas 0d93e74 — src/middlewared/middlewared/plugins/container bridge.py

Disable STP on the container bridge

## Problem
truenasbr0 was created with pynetif's default of STP on, so every new vnet port spent ~30s in listening/learning before forwarding. DHCP to dnsmasq was dropped during that window, delaying container networking by 30s or more on every start.

## Solution
Create the bridge with STP off. It has no physical members and only carries container NICs, so there is nothing for STP to protect; incus's bridge behaved the same way.
DeltaFile
+1-1src/middlewared/middlewared/plugins/container/bridge.py
+1-11 files

FreeNAS/freenas 102c504 — src/middlewared/middlewared/plugins/container migrate.py crud.py, src/middlewared/middlewared/plugins/vm crud.py

Validate container configuration values when they are saved

## Problem
Several container settings passed create and update but only failed at start, often as a raw IndexError or ValueError: an unparseable or empty init, a cpuset with whitespace or CPUs that are not online, and a memory limit above host RAM. A missing initdir made start report success for a container that exited immediately.

## Solution
- **Save time**: init must parse to at least one token; cpuset must be well formed and online, and memory within host RAM. The cpuset and memory checks only run when the value changes, so a host that has since lost CPUs or memory does not block unrelated edits.
- **Start time**: initdir must resolve to a directory inside the container root.
- **Migration**: an incus CPU count, or a CPU set or memory limit this host can't satisfy, is dropped with a job log line instead of failing the container.
- An invalid pinned VM cpuset is a validation error instead of an unhandled ValueError, and autostart failures are logged with a traceback. Accepting `0-0` relies on the matching truenas_pylibvirt parser change.
DeltaFile
+44-3src/middlewared/middlewared/plugins/container/lifecycle.py
+34-11src/middlewared/middlewared/plugins/container/crud.py
+45-0src/middlewared/middlewared/pytest/unit/plugins/container/test_config_values.py
+22-2src/middlewared/middlewared/plugins/container/migrate.py
+12-6src/middlewared/middlewared/plugins/vm/crud.py
+7-0src/middlewared/middlewared/utils/cpu/info.py
+164-221 files not shown
+166-237 files

FreeNAS/freenas 495efb5 — src/middlewared/middlewared/plugins/container migrate.py lifecycle.py, src/middlewared/middlewared/plugins/vm crud.py

Validate container configuration values when they are saved

## Problem
Several container settings passed create and update but only failed at start, often as a raw IndexError or ValueError: an unparseable or empty init, a cpuset with whitespace or CPUs that are not online, and a memory limit above host RAM. A missing initdir made start report success for a container that exited immediately.

## Solution
- **Save time**: init must parse to at least one token; cpuset must be well formed and online, and memory within host RAM. The cpuset and memory checks only run when the value changes, so a host that has since lost CPUs or memory does not block unrelated edits.
- **Start time**: initdir must resolve to a directory inside the container root.
- **Migration**: an incus CPU count, or a CPU set or memory limit this host can't satisfy, is dropped with a job log line instead of failing the container.
- An invalid pinned VM cpuset is a validation error instead of an unhandled ValueError, and autostart failures are logged with a traceback. Accepting `0-0` relies on the matching truenas_pylibvirt parser change.
DeltaFile
+34-11src/middlewared/middlewared/plugins/container/crud.py
+45-0src/middlewared/middlewared/pytest/unit/plugins/container/test_config_values.py
+27-2src/middlewared/middlewared/plugins/container/lifecycle.py
+22-2src/middlewared/middlewared/plugins/container/migrate.py
+12-6src/middlewared/middlewared/plugins/vm/crud.py
+7-0src/middlewared/middlewared/utils/cpu/info.py
+147-211 files not shown
+149-227 files

FreeNAS/freenas a2668ed — src/middlewared/middlewared/test/integration/assets pool.py, tests/api2 test_audit_system.py test_draid_record_and_block_size.py

Ban `pytest.skip`
DeltaFile
+10-27tests/api2/test_zfs_resource_create.py
+30-0tests/api2/zfs_tier/test_config_nonenterprise.py
+4-19tests/api2/zfs_tier/test_config.py
+23-0src/middlewared/middlewared/test/integration/assets/pool.py
+5-16tests/api2/test_draid_record_and_block_size.py
+2-11tests/api2/test_audit_system.py
+74-7318 files not shown
+127-12824 files

FreeNAS/freenas c19953c — src/middlewared/middlewared/test/integration/assets pool.py, tests/api2 test_audit_system.py test_draid_record_and_block_size.py

Ban `pytest.skip`
DeltaFile
+10-27tests/api2/test_zfs_resource_create.py
+30-0tests/api2/zfs_tier/test_config_nonenterprise.py
+4-19tests/api2/zfs_tier/test_config.py
+23-0src/middlewared/middlewared/test/integration/assets/pool.py
+5-16tests/api2/test_draid_record_and_block_size.py
+2-11tests/api2/test_audit_system.py
+74-7318 files not shown
+127-12824 files

FreeNAS/freenas 2c192ce — src/middlewared/middlewared event.py main.py, src/middlewared/middlewared/api/base/server event.py

Document the event payloads pool.scan and the event sources actually send
DeltaFile
+58-0src/middlewared/middlewared/pytest/unit/api/base/server/test_event_source_models.py
+33-1src/middlewared/middlewared/api/base/server/event.py
+2-11src/middlewared/middlewared/main.py
+2-5src/middlewared/middlewared/api/v28_0_0/zfs_tier.py
+4-0src/middlewared/middlewared/event.py
+4-0src/middlewared/middlewared/api/v28_0_0/pool_scrub.py
+103-171 files not shown
+106-177 files

FreeNAS/freenas b4f2435 — src/middlewared/middlewared/pytest/unit/api/base test_secret_default_wrap.py, src/middlewared/middlewared/utils pydantic_.py

Document a Secret field's default as its value in the API schema
DeltaFile
+15-1src/middlewared/middlewared/utils/pydantic_.py
+15-0src/middlewared/middlewared/pytest/unit/api/base/test_secret_default_wrap.py
+30-12 files

FreeNAS/freenas 8b82566 — src/middlewared/middlewared/api/v28_0_0 kerberos.py interface.py, src/middlewared/middlewared/plugins kerberos.py

Make interface.lag_supported_protocols and kerberos.keytab.kerberos_principal_choices public
DeltaFile
+8-3src/middlewared/middlewared/plugins/interface/lag_protocols.py
+11-0src/middlewared/middlewared/api/v28_0_0/kerberos.py
+11-0src/middlewared/middlewared/api/v28_0_0/interface.py
+7-1src/middlewared/middlewared/plugins/kerberos.py
+37-44 files

FreeNAS/freenas ece9d19 — src/middlewared/middlewared/etc_files/webshare config.json.py, src/middlewared/middlewared/plugins/zfs tier.py

NAS-141340 / 28.0.0-BETA.1 / Set WebShare no_dataset_traversal when tiering is enabled (#20150)

When ZFS tiering is enabled, the tier contract is authoritative for what
each share exposes, so WebShare must not traverse from a share root into
a child dataset mounted inside it. This is the WebShare counterpart of
`shadow:no_dataset_traversal` in smb.conf. The config contract is
defined in
https://github.com/iXsystems/truenas-file-manager/issues/2232.

Jira: https://ixsystems.atlassian.net/browse/NAS-141340

- `/etc/webshare/config.json` gets a top-level `no_dataset_traversal`
boolean that follows `zfs.tier.config.enabled`.
- `zfs.tier.update` reloads WebShare when `enabled` changes, so new
sessions pick up the change.
- `tests/api2/zfs_tier/test_webshare.py` checks the rendered flag in
both states, and that a WebShare session lists and opens a child dataset
with tiering off, and hides it and refuses `directory.getHandle` with
`dataset_boundary` with tiering on. `WebshareSession` gains

    [4 lines not shown]
DeltaFile
+101-0tests/api2/zfs_tier/test_webshare_tiering.py
+48-4src/middlewared/middlewared/test/integration/assets/webshare.py
+5-1src/middlewared/middlewared/plugins/zfs/tier.py
+5-0src/middlewared/middlewared/etc_files/webshare/config.json.py
+159-54 files

FreeNAS/freenas 9147be6 — tests runtest.py

NAS-145133 / 27.1.0 / Accept `--enterprise_tests` (by themylogin) (#20160)

Original PR: https://github.com/truenas/middleware/pull/20158

Co-authored-by: themylogin <themylogin at gmail.com>
DeltaFile
+6-0tests/runtest.py
+6-01 files

FreeNAS/freenas 2e9383f — tests runtest.py

NAS-145133 / 27.0.0 / Accept `--enterprise_tests` (by themylogin) (#20159)

Original PR: https://github.com/truenas/middleware/pull/20158

Co-authored-by: themylogin <themylogin at gmail.com>
DeltaFile
+6-0tests/runtest.py
+6-01 files

FreeNAS/freenas 7c87818 — src/middlewared/middlewared/plugins/directoryservices_ datastore.py, tests/directory_services test_activedirectory_idmap.py

Reject duplicate idmap domain names and copied idmap ranges

(cherry picked from commit 6c3d3f9feee2e4af422f0efd7779ec0d590ff0fb)
DeltaFile
+283-9tests/unit/test_directoryservices_idmap_ranges.py
+51-15src/middlewared/middlewared/plugins/directoryservices_/datastore.py
+50-0tests/directory_services/test_activedirectory_idmap.py
+384-243 files

FreeNAS/freenas bcf2a84 — src/middlewared/middlewared/plugins/directoryservices_ datastore.py, src/middlewared/middlewared/utils/directoryservices credential.py

Remove deepcopy nonsense

(cherry picked from commit 41c9a2a17c31acdadea11b8088748460e13b195c)
DeltaFile
+41-17tests/unit/test_directoryservices_idmap_ranges.py
+11-7src/middlewared/middlewared/utils/directoryservices/credential.py
+1-3src/middlewared/middlewared/plugins/directoryservices_/datastore.py
+53-273 files

FreeNAS/freenas da276b7 — src/middlewared/middlewared/plugins/directoryservices_ datastore.py, tests/unit test_directoryservices_idmap_ranges.py

Do not exempt the AD backend from the idmap overlap check

Samba documents idmap ranges as disjoint. If two domains hold the same
uidNumber, both accounts get the same UID and winbind does not report it.

- Reject new overlaps between domains that use the AD backend. This only
  changes the primary domain against a trusted domain, as the API model
  already rejects overlaps between trusted domains.
- Saved overlaps are still left alone.

(cherry picked from commit 9ca1fc88853306c9dd83239c076298cfb7c9ff2c)
DeltaFile
+17-5tests/unit/test_directoryservices_idmap_ranges.py
+0-4src/middlewared/middlewared/plugins/directoryservices_/datastore.py
+17-92 files

FreeNAS/freenas 1f47460 — src/middlewared/middlewared/plugins/directoryservices_ datastore.py, tests/directory_services test_activedirectory_idmap.py

Reject new overlaps between Active Directory idmap ranges

The directory services refactor (368cc8cf63) removed the idmap plugin's range
validation and kept only a check between trusted domains. A trusted domain
could then share IDs with the primary domain or BUILTIN.

- Check BUILTIN, the primary domain and enabled trusted domains in
  directoryservices.update.
- Allow overlap between two domains that use the AD backend, as the idmap
  plugin did (a79a0d3c6e) and as testparm does.
- Leave overlaps already in the saved configuration alone, so that existing
  setups can still be disabled, enabled and saved.

(cherry picked from commit d7f41b5beb8a7486f3bc783664f89ac595f5fcd8)
DeltaFile
+211-0tests/unit/test_directoryservices_idmap_ranges.py
+51-0src/middlewared/middlewared/plugins/directoryservices_/datastore.py
+16-0tests/directory_services/test_activedirectory_idmap.py
+278-03 files

FreeNAS/freenas 6347b54 — src/middlewared/middlewared/plugins/directoryservices_ datastore.py, src/middlewared/middlewared/utils/directoryservices credential.py

NAS-142154 / 28.0.0-BETA.1 / Reject new overlaps between Active Directory idmap ranges (#20148)

The directory services refactor (368cc8cf63) removed the idmap plugin's
range validation and kept only a check between trusted domains. A
trusted domain could then share IDs with the primary domain or BUILTIN.

- Check BUILTIN, the primary domain and enabled trusted domains in
directoryservices.update.
- Leave overlaps already in the saved configuration alone, so that
existing setups can still be disabled, enabled and saved.
DeltaFile
+521-0tests/unit/test_directoryservices_idmap_ranges.py
+81-0src/middlewared/middlewared/plugins/directoryservices_/datastore.py
+66-0tests/directory_services/test_activedirectory_idmap.py
+11-7src/middlewared/middlewared/utils/directoryservices/credential.py
+679-74 files

FreeNAS/freenas c8f1c7b — tests runtest.py

Accept `--enterprise_tests`

(cherry picked from commit 41b18c18fa6a85cae7c89f2950569a63384dedf4)
DeltaFile
+6-0tests/runtest.py
+6-01 files

FreeNAS/freenas b6d2132 — src/middlewared/middlewared/etc_files/webshare config.json.py, src/middlewared/middlewared/plugins/zfs tier.py

Set WebShare no_dataset_traversal when tiering is enabled

When ZFS tiering is enabled, WebShare must not traverse from a share root
into child datasets mounted inside it, the same as Samba does with
`shadow:no_dataset_traversal`. Render a top-level `no_dataset_traversal`
boolean in /etc/webshare/config.json that follows zfs.tier.config.enabled,
and reload WebShare when tiering is toggled so new sessions pick it up.

The zfs_tier integration tests check the rendered flag and that a WebShare
session hides and refuses a child dataset only while tiering is enabled.
DeltaFile
+101-0tests/api2/zfs_tier/test_webshare_tiering.py
+48-4src/middlewared/middlewared/test/integration/assets/webshare.py
+5-1src/middlewared/middlewared/plugins/zfs/tier.py
+5-0src/middlewared/middlewared/etc_files/webshare/config.json.py
+159-54 files

FreeNAS/freenas 02a7736 — tests runtest.py

Accept `--enterprise_tests`

(cherry picked from commit 41b18c18fa6a85cae7c89f2950569a63384dedf4)
DeltaFile
+6-0tests/runtest.py
+6-01 files

FreeNAS/freenas 1c1ce8e — src/middlewared/middlewared job.py, tests/api2 test_client_job.py test_job_events.py

Send one terminal `core.get_jobs` event per job

A successful job sent its SUCCESS event twice. `__run_body` sets the
state to SUCCESS and then calls `set_progress(100)`, which sends an
event. The `finally` block in `Job.run` then sends the same event again.

The client usually drops the second event, because `Job.result` removes
the job first. When the caller thread wakes up late, the job callback
runs twice. This made `test_client_job_callback` flaky.

`set_progress` no longer sends an event for a finished job. `Job.run`
sends the only event that reports the final state. Progress still goes
to the jobs that wrap the finished job.

`test_job_events.py` waits on `core.job_wait` before it checks the
events, so it catches a duplicate event on every run. New tests cover
failed, aborted and wrapped jobs.

`test_client_job_callback` loses its `flaky` marker. It no longer

    [5 lines not shown]
DeltaFile
+117-20tests/api2/test_job_events.py
+12-14tests/api2/test_client_job.py
+2-1src/middlewared/middlewared/job.py
+131-353 files

FreeNAS/freenas 6adfe2f —

Empty commit to create PR on github.

You should reset it
DeltaFile
+0-00 files

FreeNAS/freenas af1adbb —

Empty commit to create PR on github.

You should reset it
DeltaFile
+0-00 files

FreeNAS/freenas 38db7b9 — src/middlewared/middlewared/test/integration/runner config.py args.py

NAS-145133 / 28.0.0-BETA.1 / Accept `--enterprise_tests` (#20158)
DeltaFile
+6-0src/middlewared/middlewared/test/integration/runner/args.py
+1-0src/middlewared/middlewared/test/integration/runner/config.py
+7-02 files

FreeNAS/freenas 05d6a8b — src/middlewared/middlewared/test/integration/assets pool.py, src/middlewared/middlewared/test/integration/utils pytest.py

Ban `pytest.skip`
DeltaFile
+6-26tests/api2/test_zfs_resource_create.py
+23-0src/middlewared/middlewared/test/integration/assets/pool.py
+5-16tests/api2/test_draid_record_and_block_size.py
+2-11tests/api2/test_audit_system.py
+6-7tests/api2/test_interface_bridge_member_busy.py
+9-2src/middlewared/middlewared/test/integration/utils/pytest.py
+51-6210 files not shown
+70-10416 files