FreeNAS/freenas 130ec07src/middlewared/middlewared/plugins replication.py zettarepl.py, tests/api2 test_replication.py

Allow running disabled replication tasks

(cherry picked from commit 4ab455e78b755250d05c5a7547acad9f76b98c1a)
DeltaFile
+8-4tests/api2/test_replication.py
+4-4src/middlewared/middlewared/plugins/zettarepl.py
+0-3src/middlewared/middlewared/plugins/replication.py
+12-113 files

FreeNAS/freenas 56a49f0src/middlewared/middlewared/api/v26_0_0 container.py, src/middlewared/middlewared/migration 0020_repair_incus_clone_origins.py

NAS-141666 / 26.0.0-RC.1 / Relocate migrated container origins out of legacy .ix-virt (#19351)
DeltaFile
+251-2src/middlewared/middlewared/plugins/container/migrate.py
+74-0src/middlewared/middlewared/migration/0020_repair_incus_clone_origins.py
+49-10src/middlewared/middlewared/plugins/container/container.py
+34-1tests/api2/test_container.py
+12-6src/middlewared/middlewared/plugins/container/lifecycle.py
+13-1src/middlewared/middlewared/api/v26_0_0/container.py
+433-205 files not shown
+446-2911 files

FreeNAS/freenas e2129absrc/middlewared/middlewared/api/v27_0_0 container.py, src/middlewared/middlewared/migration 0020_repair_incus_clone_origins.py

Relocate migrated container origins out of legacy .ix-virt

## Problem
Incus containers are ZFS clones of an image snapshot. The incus->container auto-migration relocated each container from `<pool>/.ix-virt/containers/<name>` to `<pool>/.truenas_containers/containers/<name>` with a bare `zfs rename` and did nothing else. A `zfs rename` does not change a clone's `origin`, so a migrated container stayed a clone of a snapshot still living inside `.ix-virt` - and deleting `.ix-virt` recursively destroyed those origin snapshots and cascaded into the dependent migrated clones, silently destroying migrated containers. The migration also left the legacy parents mounted, moved on without a license, gave up on every remaining pool when one failed, and could leave a container renamed into the native tree with no database row pointing at it.

## Solution
Relocate each container's origin image out of `.ix-virt` before renaming the container, so no migrated container depends on anything under `.ix-virt`, and make the surrounding migration and deletion paths recoverable.

- **Shared relocation helper** - `relocate_container_origin` reads a container's live `origin`; if it points at an image under `.ix-virt/images` or `.ix-virt/deleted/images`, it sets `canmount=noauto` on that image dataset and then renames it into the native `.truenas_containers/images/` tree. The rename goes last so it is the single atomic commit point: the image is either wholly still in `.ix-virt` or wholly relocated, and the return value describes reality. Fan-out clones auto-repoint on the rename; an origin outside `.ix-virt` is left alone; a container that is a clone of another container is refused, since the two would arrive entangled.
- **Migration path** - the migration calls the helper immediately before renaming each container and skips any container whose base image cannot be relocated, rather than producing one that looks healthy until `.ix-virt` is deleted.
- **Repair migration** - new `0020_repair_incus_clone_origins` runs the same relocation over existing `container.container` rows for systems that already ran the old migration, and restores the legacy parents' mountpoint those runs left inherited.
- **Leave the legacy tree as found** - mount properties are reverted on any container that is not migrated, and the legacy parents are restored at the end of every run, so nothing is left mounted under `/mnt/<pool>/.ix-virt` with nothing managing it. Both reverts are armed before the properties are touched, since a partial apply has to be reverted too.
- **Migration robustness** - the migration is skipped on HA capable hardware and deferred (leaving the legacy configuration intact) when the system is not licensed for containers; one unusable pool no longer stops the pools after it; and a container whose dataset was renamed but whose row was never created is moved back where the user can see it.
- **Safer deletion** - the libvirt domain is torn down first so the container's runtime mounts are gone before ZFS is touched, the dataset is destroyed next, and the database records are removed only once it is confirmed gone - so a failed destroy never orphans the dataset with no row pointing at it. An already-missing dataset is tolerated so a container whose data was lost to the old cascade can still be removed cleanly.
- **Active-instance guards** - deleting or renaming a container that is not stopped (running or suspended) is refused; delete additionally accepts `force=True`, mirroring the VM delete flow, and is now a job locked per container id. The container status model gains the `SUSPENDED` state it has always been able to report.
DeltaFile
+250-2src/middlewared/middlewared/plugins/container/migrate.py
+73-0src/middlewared/middlewared/migration/0020_repair_incus_clone_origins.py
+42-1tests/api2/test_container.py
+33-9src/middlewared/middlewared/plugins/container/__init__.py
+35-5src/middlewared/middlewared/plugins/container/crud.py
+14-2src/middlewared/middlewared/api/v27_0_0/container.py
+447-194 files not shown
+469-2810 files

FreeNAS/freenas 31cab44src/middlewared/middlewared/api/v27_0_0 container.py, src/middlewared/middlewared/migration 0020_repair_incus_clone_origins.py

Relocate migrated container origins out of legacy .ix-virt

## Problem
Incus containers are ZFS clones of an image snapshot. The incus->container auto-migration relocated each container from `<pool>/.ix-virt/containers/<name>` to `<pool>/.truenas_containers/containers/<name>` with a bare `zfs rename` and did nothing else. A `zfs rename` does not change a clone's `origin`, so a migrated container stayed a clone of a snapshot still living inside `.ix-virt` - and deleting `.ix-virt` recursively destroyed those origin snapshots and cascaded into the dependent migrated clones, silently destroying migrated containers. The migration also left the legacy parents mounted, moved on without a license, gave up on every remaining pool when one failed, and could leave a container renamed into the native tree with no database row pointing at it.

## Solution
Relocate each container's origin image out of `.ix-virt` before renaming the container, so no migrated container depends on anything under `.ix-virt`, and make the surrounding migration and deletion paths recoverable.

- **Shared relocation helper** - `relocate_container_origin` reads a container's live `origin`; if it points at an image under `.ix-virt/images` or `.ix-virt/deleted/images`, it sets `canmount=noauto` on that image dataset and then renames it into the native `.truenas_containers/images/` tree. The rename goes last so it is the single atomic commit point: the image is either wholly still in `.ix-virt` or wholly relocated, and the return value describes reality. Fan-out clones auto-repoint on the rename; an origin outside `.ix-virt` is left alone; a container that is a clone of another container is refused, since the two would arrive entangled.
- **Migration path** - the migration calls the helper immediately before renaming each container and skips any container whose base image cannot be relocated, rather than producing one that looks healthy until `.ix-virt` is deleted.
- **Repair migration** - new `0020_repair_incus_clone_origins` runs the same relocation over existing `container.container` rows for systems that already ran the old migration, and restores the legacy parents' mountpoint those runs left inherited.
- **Leave the legacy tree as found** - mount properties are reverted on any container that is not migrated, and the legacy parents are restored at the end of every run, so nothing is left mounted under `/mnt/<pool>/.ix-virt` with nothing managing it. Both reverts are armed before the properties are touched, since a partial apply has to be reverted too.
- **Migration robustness** - the migration is skipped on HA capable hardware and deferred (leaving the legacy configuration intact) when the system is not licensed for containers; one unusable pool no longer stops the pools after it; and a container whose dataset was renamed but whose row was never created is moved back where the user can see it.
- **Safer deletion** - the libvirt domain is torn down first so the container's runtime mounts are gone before ZFS is touched, the dataset is destroyed next, and the database records are removed only once it is confirmed gone - so a failed destroy never orphans the dataset with no row pointing at it. An already-missing dataset is tolerated so a container whose data was lost to the old cascade can still be removed cleanly.
- **Active-instance guards** - deleting or renaming a container that is not stopped (running or suspended) is refused; delete additionally accepts `force=True`, mirroring the VM delete flow, and is now a job locked per container id. The container status model gains the `SUSPENDED` state it has always been able to report.
DeltaFile
+250-2src/middlewared/middlewared/plugins/container/migrate.py
+73-0src/middlewared/middlewared/migration/0020_repair_incus_clone_origins.py
+53-10src/middlewared/middlewared/plugins/container/crud.py
+42-1tests/api2/test_container.py
+33-9src/middlewared/middlewared/plugins/container/__init__.py
+14-2src/middlewared/middlewared/api/v27_0_0/container.py
+465-246 files not shown
+492-3712 files

FreeNAS/freenas 6a9283a

Empty commit to create PR on github.

You should reset it
DeltaFile
+0-00 files

FreeNAS/freenas 6087fadsrc/middlewared/middlewared/plugins/container container.py, src/middlewared/middlewared/plugins/zfs utils.py

Skip adding truenas containers to internal paths and don't use force
DeltaFile
+1-1src/middlewared/middlewared/plugins/container/container.py
+0-1src/middlewared/middlewared/plugins/zfs/utils.py
+1-22 files

FreeNAS/freenas ba60a7etests/sharing_protocols/iscsi test_266_iscsi_stale_enable_work.py

add tests for sysfs iscsi panic
DeltaFile
+4-3tests/sharing_protocols/iscsi/test_266_iscsi_stale_enable_work.py
+4-31 files

FreeNAS/freenas 47581a9tests/sharing_protocols/iscsi test_266_iscsi_stale_enable_work.py

add tests for sysfs iscsi panic
DeltaFile
+179-0tests/sharing_protocols/iscsi/test_266_iscsi_stale_enable_work.py
+179-01 files

FreeNAS/freenas 286f170src/middlewared/middlewared/plugins/failover_ ha_hardware.py, src/middlewared/middlewared/plugins/truenas entitlements.py

Route HA licensing through the entitlement engine

This commit adds changes to make is_licensed_for_ha answer out of POLICY rather than comparing the license type itself, so there is one place to change the HA rule. Behaviour is unchanged since LicenseTypeRule evaluates the same predicate over the same get_license, and it stays a plain function with no middleware dependency because ha_panic imports it directly and runs when middlewared may already be gone.

This also drops is_ha_capable from EntitlementFacts as no rule ever read it, which keeps the HA path clear of platform detection and takes a failover.hardware round-trip off every entitlement check.
DeltaFile
+39-0src/middlewared/middlewared/pytest/unit/plugins/test_failover.py
+27-8src/middlewared/middlewared/pytest/unit/utils/test_entitlements.py
+21-4src/middlewared/middlewared/plugins/failover_/ha_hardware.py
+24-0src/middlewared/middlewared/pytest/unit/utils/test_license_legacy_utils.py
+5-6src/middlewared/middlewared/plugins/truenas/entitlements.py
+3-1src/middlewared/middlewared/utils/entitlements/policy.py
+119-192 files not shown
+120-228 files

FreeNAS/freenas e983718src/middlewared/middlewared/plugins/vm lifecycle.py, src/middlewared/middlewared/pytest/unit/plugins/vm test_vm_clock_xml.py

Apply the VM system clock setting to the generated domain

## Problem
`pylibvirt_vm()` builds the domain configuration by unpacking `VMEntry.model_dump()` into a plain dataclass, converting `bootloader` and `cpu_mode` into their enums but not `time`. The API model types `time` as a string literal, so the configuration held `'LOCAL'`/`'UTC'` instead of a `Time` member, and the clock offset is decided by comparing that field against `Time.LOCAL`. That comparison was always false, so every VM was defined with `<clock offset="utc">` regardless of what the user picked, and Windows guests — which expect a localtime RTC — ran off by the host's UTC offset. Containers were unaffected because their equivalent helper already does the conversion.

## Solution
Convert `time` alongside the other two enums. Every caller reaches this through `VMEntry`, whose `Literal['LOCAL', 'UTC']` guarantees a valid value, so the conversion cannot raise. Added a unit test covering both offsets with and without Hyper-V enlightenments, which restores coverage that was dropped when XML generation moved out to truenas_pylibvirt.
DeltaFile
+46-0src/middlewared/middlewared/pytest/unit/plugins/vm/test_vm_clock_xml.py
+2-1src/middlewared/middlewared/plugins/vm/lifecycle.py
+48-12 files

FreeNAS/freenas dc90308src/middlewared/middlewared/plugins/pool_ dataset.py, tests/api2 test_pool_dataset_encryption.py

Pass the requested pbkdf2 iteration count to ZFS when creating a dataset
DeltaFile
+10-0tests/api2/test_pool_dataset_encryption.py
+1-5src/middlewared/middlewared/plugins/pool_/dataset.py
+11-52 files

FreeNAS/freenas 84129f0src/middlewared/middlewared/plugins/system __init__.py product.py, src/middlewared/middlewared/utils/entitlements matrix.py

Remove dead writers of the autotune setting

This commit adds changes to drop the two code paths that flipped the autotune advanced setting on enterprise systems, one on the post license update hook and one on firstboot. Nothing has read that column since the linux port removed its consumer, and the tuner itself runs from the installer without consulting either the column or the license, so both writers were setting a value that never did anything. The column, its API field and the legacy license key injection are deliberately left in place.
DeltaFile
+0-9src/middlewared/middlewared/plugins/system/product.py
+0-4src/middlewared/middlewared/plugins/system/__init__.py
+1-0src/middlewared/middlewared/utils/entitlements/matrix.py
+1-133 files

FreeNAS/freenas 35f5165src/middlewared/middlewared/pytest/unit/utils test_license_utils.py test_license_legacy_utils.py, src/middlewared/middlewared/utils/license __init__.py constants.py

Format license utils package

This commit fixes an issue where the files added when license retrieval moved into a shared utils package were never run through ruff format, so the formatter check fails on them. Reformatting only reflows multi-line collection literals and parametrize arguments to match line length; no logic changes.
DeltaFile
+106-64src/middlewared/middlewared/pytest/unit/utils/test_license_legacy_utils.py
+38-24src/middlewared/middlewared/utils/license/legacy.py
+17-15src/middlewared/middlewared/utils/license/constants.py
+8-6src/middlewared/middlewared/utils/license/__init__.py
+9-3src/middlewared/middlewared/pytest/unit/utils/test_license_utils.py
+178-1125 files

FreeNAS/freenas d62f4a9src/middlewared/middlewared/api/v26_0_0 nfs.py, src/middlewared/middlewared/plugins nfs.py

Add missing coverage for NFS snapshot entitlement

This commit adds changes to register the bespoke NFS snapshot denial message for all three deny reasons rather than just the two currently reachable, so editing a matrix cell later cannot silently drop the wording back to the generic template.

It also covers the feature against the live policy instead of only the matrix fixture, and reworks the unentitled integration fixture to mock the license rather than the entitlement check, so the negative path exercises the real engine instead of asserting a string the test itself handed in.
DeltaFile
+42-2src/middlewared/middlewared/pytest/unit/utils/test_entitlements.py
+8-10tests/sharing_protocols/nfs/test_nfs_snapdir.py
+2-3src/middlewared/middlewared/plugins/nfs.py
+2-2src/middlewared/middlewared/api/v26_0_0/nfs.py
+1-2src/middlewared/middlewared/utils/entitlements/engine.py
+55-195 files

FreeNAS/freenas c87d1c4src/middlewared/middlewared/plugins smb.py, src/middlewared/middlewared/plugins/smb_ util_smbconf.py

Gate SMB fast path and Veeam shares through the entitlement engine

This commit adds changes to gate the SMB ZFS fastpath parameters and Veeam repository shares through the entitlement engine instead of a bare is_enterprise read. Both stay separate flags with their own matrix vectors and remain in the enterprise-only legacy injection bucket, so existing licensed systems keep both features and only unlicensed HA-capable boxes lose them.
DeltaFile
+42-0src/middlewared/middlewared/pytest/unit/utils/test_entitlements.py
+8-4tests/api2/test_smb_veeam_repo.py
+6-4src/middlewared/middlewared/plugins/smb.py
+7-0src/middlewared/middlewared/utils/entitlements/engine.py
+3-3src/middlewared/middlewared/plugins/smb_/util_smbconf.py
+5-0src/middlewared/middlewared/plugins/truenas/entitlements.py
+71-112 files not shown
+74-128 files

FreeNAS/freenas 65fbfacsrc/middlewared/middlewared/plugins/nvmet global.py, src/middlewared/middlewared/plugins/truenas license_legacy_utils.py

Flip NVMe-oF SPDK onto its matrix vector

This commit adds changes to move NVMEOF_SPDK off its transitional legacy rule and onto its matrix vector, which drops the is_ha_capable disjunct. That was a licensing proxy inherited from product_type rather than a physical prerequisite, so HA capable systems holding no feature key no longer get SPDK without a license.

The key is now injected into every legacy license instead of only enterprise models, so existing legacy holders keep SPDK on upgrade.
DeltaFile
+22-20src/middlewared/middlewared/pytest/unit/utils/test_entitlements.py
+0-18src/middlewared/middlewared/utils/entitlements/legacy.py
+5-2src/middlewared/middlewared/plugins/truenas/license_legacy_utils.py
+1-1src/middlewared/middlewared/utils/entitlements/policy.py
+1-1src/middlewared/middlewared/pytest/unit/plugins/truenas/test_license_legacy_utils.py
+1-1src/middlewared/middlewared/plugins/nvmet/global.py
+30-436 files

FreeNAS/freenas 96600dfsrc/middlewared/middlewared/plugins truesearch.py, src/middlewared/middlewared/pytest/unit/plugins test_truesearch.py

Gate TrueSearch through the entitlement engine

This commit adds changes to route the TrueSearch gate through the entitlement engine and drop the TrueNAS Connect disjunct, since a system that connects to TNC is issued a license carrying the key rather than being entitled by the connection itself. This also tightens the check from mere license presence to the feature key, which is what the feature matrix asks for, and the denial reason now comes from the entitlement instead of a hardcoded string naming TNC as an alternative.
DeltaFile
+66-0src/middlewared/middlewared/pytest/unit/plugins/test_truesearch.py
+5-5src/middlewared/middlewared/plugins/truesearch.py
+5-2src/middlewared/middlewared/pytest/unit/utils/test_entitlements.py
+1-0src/middlewared/middlewared/utils/entitlements/policy.py
+1-0src/middlewared/middlewared/utils/entitlements/engine.py
+78-75 files

FreeNAS/freenas c3887e9src/middlewared/middlewared/plugins/truenas entitlements.py, src/middlewared/middlewared/pytest/unit/utils test_entitlements.py

Rename check func
DeltaFile
+28-28src/middlewared/middlewared/pytest/unit/utils/test_entitlements.py
+2-2src/middlewared/middlewared/utils/entitlements/__init__.py
+1-1src/middlewared/middlewared/utils/entitlements/policy.py
+1-1src/middlewared/middlewared/plugins/truenas/entitlements.py
+32-324 files

FreeNAS/freenas 04ff3f6src/middlewared/middlewared/plugins/webshare sharing.py, src/middlewared/middlewared/pytest/unit/utils test_license_legacy_utils.py test_entitlements.py

Gate Webshare through the entitlement engine

This commit adds changes to gate Webshare through the entitlement engine. Webshare had no license check at all until now, so the key is injected into every legacy license to make sure no existing licensee loses their shares on upgrade.

Enforcement is set-time only, on share creation and on the service config. An update that leaves a share disabled is let through without the entitlement, so a system that has lost it can still turn a share off rather than having to delete it. Existing shares keep serving, since nothing re-reads the entitlement after the fact.

Note the WebUI currently hides Webshare from Enterprise systems and shows it to Community Edition, which is the inverse of this gate. That needs a matching change on the UI side before the two agree.
DeltaFile
+15-2tests/api2/test_sharing_path_resolution_hooks.py
+13-1tests/api2/test_webshare_homedir.py
+10-0src/middlewared/middlewared/plugins/webshare/sharing.py
+6-2src/middlewared/middlewared/pytest/unit/utils/test_entitlements.py
+5-0src/middlewared/middlewared/utils/license/legacy.py
+5-0src/middlewared/middlewared/pytest/unit/utils/test_license_legacy_utils.py
+54-53 files not shown
+58-59 files

FreeNAS/freenas a4d6019src/middlewared/middlewared/plugins/system_general update.py, src/middlewared/middlewared/pytest/unit/utils test_entitlements.py

Gate directory services authentication through the entitlement engine

This commit adds changes to gate ds_auth, which controls whether directory services accounts may authenticate to the UI and API, through the entitlement engine instead of a bare is_enterprise read. Directory services themselves stay completely ungated on every product including Community Edition, so AD, LDAP and IPA are unaffected and only the UI and API login path is licensed. DIRECTORY_SERVICES also moves into the unconditional legacy injection bucket so every legacy licensee keeps it regardless of model.
DeltaFile
+36-2src/middlewared/middlewared/pytest/unit/utils/test_entitlements.py
+16-8tests/directory_services/test_activedirectory_basic.py
+9-5tests/directory_services/test_ipa_join.py
+9-3tests/directory_services/test_ldap_basic.py
+6-5src/middlewared/middlewared/plugins/system_general/update.py
+8-3src/middlewared/middlewared/utils/license/legacy.py
+84-264 files not shown
+103-3010 files

FreeNAS/freenas b6b862fsrc/middlewared/middlewared/plugins/security info.py, src/middlewared/middlewared/pytest/unit/plugins test_security_info.py

Gate STIG and FIPS mode through the entitlement engine

This commit adds changes to route the security config gate through the entitlement engine instead of checking for the mere presence of a license. FIPS has no key of its own so the STIG entitlement governs every enterprise security option, matching the single STIG row in the feature matrix. The check stays on the write path only, since consuming it on the read path would silently unharden PAM on the next login.
DeltaFile
+38-0src/middlewared/middlewared/pytest/unit/plugins/test_security_info.py
+19-6src/middlewared/middlewared/pytest/unit/utils/test_entitlements.py
+6-2src/middlewared/middlewared/plugins/security/info.py
+1-0src/middlewared/middlewared/utils/entitlements/policy.py
+1-0src/middlewared/middlewared/utils/entitlements/engine.py
+65-85 files

FreeNAS/freenas 6b5f321src/middlewared/middlewared/plugins support.py, src/middlewared/middlewared/plugins/nvmet global.py

Gate feature checks through the entitlement engine

This commit adds changes to route the dedup, SED, NVMe-oF SPDK, ZFS tiering and proactive support gates through truenas.entitlements.check instead of hand-composed license predicates scattered across plugins. It also fixes a crash in product_type when a license carries no hardware model, which now classifies as Community Edition.
DeltaFile
+12-18src/middlewared/middlewared/plugins/pool_/utils.py
+5-5src/middlewared/middlewared/plugins/nvmet/global.py
+5-4src/middlewared/middlewared/plugins/system/product.py
+4-3src/middlewared/middlewared/plugins/zfs/tier.py
+2-4src/middlewared/middlewared/plugins/support.py
+28-345 files

FreeNAS/freenas 908cf9csrc/middlewared/middlewared/plugins/kmip update.py, src/middlewared/middlewared/pytest/unit/utils test_license_info_wire.py test_entitlements.py

Gate KMIP through the entitlement engine

This commit adds changes to gate KMIP key management through the entitlement engine. KMIP had no license check at all until now, so the key is injected into every legacy license to make sure no existing licensee loses key management on upgrade.

Only the enable transition is gated. Disabling KMIP is how escrowed ZFS and SED keys are pulled back to the local database, so a system that loses the entitlement must still be able to turn it off and recover its keys.
DeltaFile
+9-0src/middlewared/middlewared/plugins/kmip/update.py
+5-0src/middlewared/middlewared/utils/license/legacy.py
+5-0src/middlewared/middlewared/pytest/unit/utils/test_license_legacy_utils.py
+4-0src/middlewared/middlewared/pytest/unit/utils/test_entitlements.py
+2-0src/middlewared/middlewared/utils/entitlements/policy.py
+1-0src/middlewared/middlewared/pytest/unit/utils/test_license_info_wire.py
+26-01 files not shown
+27-07 files

FreeNAS/freenas c66a27esrc/middlewared/middlewared/plugins/container container.py, src/middlewared/middlewared/plugins/truenas license_legacy_utils.py

Gate apps, containers and VMs through the entitlement engine

This commit adds changes to route the apps, containers and VMs gates through the entitlement engine rather than raw feature flag reads, and injects those keys into legacy licenses so existing licensees keep both capabilities after upgrade. Note this means an injected key now overrides the legacy jails/vm bits on HA capable hardware, which is called out in a TODO next to the injection bucket.
DeltaFile
+42-0src/middlewared/middlewared/pytest/unit/utils/test_entitlements.py
+11-12src/middlewared/middlewared/pytest/unit/plugins/truenas/test_license_legacy_utils.py
+15-8src/middlewared/middlewared/pytest/unit/plugins/test_vm.py
+9-9src/middlewared/middlewared/plugins/truenas/license_legacy_utils.py
+3-10src/middlewared/middlewared/plugins/container/container.py
+3-7src/middlewared/middlewared/plugins/vm/info.py
+83-463 files not shown
+94-539 files

FreeNAS/freenas f630d3fsrc/middlewared/middlewared/plugins/truenas license_legacy_utils.py, src/middlewared/middlewared/pytest/unit/plugins/truenas test_license_legacy_utils.py

Inject feature keys when translating legacy licenses

This commit adds changes to normalize legacy licenses at the translation layer by injecting the feature keys a legacy holder is entitled to today: capabilities gated on any valid license go to every legacy license, is_enterprise-gated capabilities go to enterprise models only, and CONTAINERS rides along with the legacy jails bit. This keeps backwards compatibility in one place so gates can evaluate the new key vocabulary uniformly.
DeltaFile
+52-8src/middlewared/middlewared/pytest/unit/plugins/truenas/test_license_legacy_utils.py
+48-3src/middlewared/middlewared/plugins/truenas/license_legacy_utils.py
+100-112 files

FreeNAS/freenas 5040590src/middlewared/middlewared/plugins/truenas license_utils.py, src/middlewared/middlewared/pytest/unit/plugins/truenas test_license_utils.py

Expose per-feature source and tier on license info

This commit adds changes to carry each licensed feature's source and tier qualifier on FeatureInfo, so entitlement rules can consume per-feature tiers (e.g. the SUPPORT contract tier) without reaching into raw license payloads.
DeltaFile
+7-1src/middlewared/middlewared/plugins/truenas/license_utils.py
+5-2src/middlewared/middlewared/pytest/unit/plugins/truenas/test_license_utils.py
+12-32 files

FreeNAS/freenas 2e30ce0src/middlewared/middlewared/plugins network.py, src/middlewared/middlewared/plugins/truenas license_legacy_utils.py

Gate FEC mode configuration through the entitlement engine

This commit adds changes to gate FEC mode configuration on the entitlement engine instead of system.is_enterprise. The physical interface check and the ethtool capability probe stay where they are, since those are hardware facts the engine does not model.

NETWORK_FEC is now injected into every legacy license rather than only enterprise models, so HA capable systems whose license carries no model or a freenas prefixed one keep being able to configure it.
DeltaFile
+33-2src/middlewared/middlewared/pytest/unit/utils/test_entitlements.py
+4-2src/middlewared/middlewared/plugins/network.py
+5-1src/middlewared/middlewared/plugins/truenas/license_legacy_utils.py
+6-0src/middlewared/middlewared/utils/entitlements/engine.py
+3-1src/middlewared/middlewared/pytest/unit/plugins/truenas/test_license_legacy_utils.py
+1-0src/middlewared/middlewared/utils/entitlements/policy.py
+52-66 files

FreeNAS/freenas 206ec98src/middlewared/middlewared/plugins nfs.py, src/middlewared/middlewared/plugins/truenas license_legacy_utils.py

Gate NFS snapshot exposure through the entitlement engine

This commit adds changes to gate NFS snapshot exposure on the entitlement engine instead of system.is_enterprise, keeping the existing validation wording via a per-feature message override.

NFS_SNAPSHOT is now injected into every legacy license rather than only enterprise models. Its matrix vector is key-only on both hardware sides, so a legacy holder whose model is freenas-prefixed would otherwise lose the export on upgrade. The trade-off is that freenas certified systems, which are denied today, gain the feature.
DeltaFile
+64-22src/middlewared/middlewared/pytest/unit/utils/test_entitlements.py
+18-4tests/sharing_protocols/nfs/test_nfs_snapdir.py
+5-5src/middlewared/middlewared/plugins/nfs.py
+6-2src/middlewared/middlewared/utils/entitlements/policy.py
+7-0src/middlewared/middlewared/utils/entitlements/engine.py
+4-2src/middlewared/middlewared/plugins/truenas/license_legacy_utils.py
+104-351 files not shown
+105-367 files

FreeNAS/freenas d541a50src/middlewared/middlewared/pytest/unit/utils test_entitlements.py, src/middlewared/middlewared/utils/entitlements legacy.py matrix.py

Add license entitlement engine

This commit adds a pure entitlement engine that resolves whether a feature is available from hardware class and license facts, using per-feature policy rules: product-matrix vectors, a support-tier rule, a license-type rule for HA, and transitional legacy rules that reproduce current gate behavior until each feature is flipped onto its matrix vector. The full product matrix ships as reference data, and completeness tests keep the policy, matrix, display names and API vocabulary in sync so a new feature flag cannot silently skip a site.
DeltaFile
+400-0src/middlewared/middlewared/pytest/unit/utils/test_entitlements.py
+185-0src/middlewared/middlewared/utils/entitlements/engine.py
+66-0src/middlewared/middlewared/utils/entitlements/policy.py
+56-0src/middlewared/middlewared/utils/entitlements/__init__.py
+45-0src/middlewared/middlewared/utils/entitlements/matrix.py
+40-0src/middlewared/middlewared/utils/entitlements/legacy.py
+792-01 files not shown
+826-07 files

FreeNAS/freenas 7844ff0src/middlewared/middlewared/plugins/jbof crud.py, src/middlewared/middlewared/pytest/unit/utils test_entitlements.py test_license_legacy_utils.py

Gate NVMe expansion shelves through the entitlement engine

This commit adds changes to gate JBOF expansion shelves through the entitlement engine rather than a bare enclosure count, keeping the shelf count itself as a conjunct and dropping the system serial check, which is a license to machine binding the license layer already owns. Legacy licenses have no way to carry a JBOF key since shelf ownership lives only in the additional hardware list, so the key is injected from the ES24N enclosure entry and licenses without a shelf are left untouched.
DeltaFile
+71-0src/middlewared/middlewared/pytest/unit/utils/test_license_legacy_utils.py
+62-2tests/api2/test_jbof.py
+43-2src/middlewared/middlewared/pytest/unit/utils/test_entitlements.py
+12-5src/middlewared/middlewared/plugins/jbof/crud.py
+14-3src/middlewared/middlewared/utils/license/legacy.py
+9-0src/middlewared/middlewared/utils/entitlements/policy.py
+211-121 files not shown
+212-127 files