mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #44052 Improve performance by reducing the time for the synchronous API call to update profiles or switch teams. And spreading out the application of profiles by processing 2000 hosts every 30 seconds. 1. **Windows profile reconciliation is no longer synchronous to bulk-set.** Apple, Android, and Apple-declaration paths still write their pending state inside the bulk-set transaction. The Windows path commits the transactional inputs and lets the existing `mdm_windows_profile_manager` cron pick the work up on its next tick. The visible effect is that `host_mdm_windows_profiles` is no longer guaranteed to be populated by the time bulk-set returns; it converges within one cron interval. 2. **The Windows reconciler now processes hosts in bounded batches, with a persisted cursor.** Previous behavior was "scan the universe of pending Windows hosts on every tick." New behavior is a host-window query bounded by batch size and a `host_uuid` cursor, advanced after the batch commits successfully and persisted across ticks. A failed tick leaves the cursor untouched so the same window is retried. 3. **Two replication races are now explicitly handled.** - Admin-delete vs reconcile: the existence check the reconciler uses to avoid touching a just-deleted profile reads from the primary, not a replica. - Insert lag in the reconciler's own listings: hosts that appear in the cursor query but are not yet visible in the scoped listings advance the cursor instead of jamming the loop. 4. **`updates.WindowsConfigProfile` from `BulkSetPendingMDMHostProfiles` is now always false in production.** The only consumer ORs it with the transactional signal from `BatchSetMDMProfiles`, which is the accurate source. The bulk-set call no longer attempts to compute or return that activity signal itself. 5. **Tests opt in to the old synchronous behavior via a named hook.** Default test behavior matches production (deferred). Legacy tests whose assertions require Windows rows immediately after bulk-set call an explicit enable-hook and rely on `t.Cleanup` to restore. # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Windows MDM profile reconciliation batching improvements enable large team transfers and bulk profile change operations to complete faster, with profile updates rolling out in the background without blocking host check-ins or other MDM activity. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
88 lines
3.5 KiB
Go
88 lines
3.5 KiB
Go
package service
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
microsoft_mdm "github.com/fleetdm/fleet/v4/server/mdm/microsoft"
|
|
"github.com/fleetdm/fleet/v4/server/platform/logging/testutils"
|
|
"github.com/fleetdm/fleet/v4/server/test"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestReconcileWindowsProfilesAfterTeamAddDeferred is the real-DB seam test
|
|
// for the production async path: BulkSetPendingMDMHostProfiles defers Windows
|
|
// reconciliation, and the install row only appears after
|
|
// ReconcileWindowsProfiles (the cron) runs.
|
|
func TestReconcileWindowsProfilesAfterTeamAddDeferred(t *testing.T) {
|
|
ds := mysql.CreateMySQLDS(t)
|
|
ctx := t.Context()
|
|
logger := testutils.TestLogger(t)
|
|
|
|
// ReconcileWindowsProfiles short-circuits to no-op when Windows MDM is
|
|
// not enabled in app config.
|
|
appCfg, err := ds.AppConfig(ctx)
|
|
require.NoError(t, err)
|
|
appCfg.MDM.WindowsEnabledAndConfigured = true
|
|
require.NoError(t, ds.SaveAppConfig(ctx, appCfg))
|
|
|
|
team, err := ds.NewTeam(ctx, &fleet.Team{Name: "deferred-recon-after-team-add"})
|
|
require.NoError(t, err)
|
|
profileUUID := mysql.InsertWindowsProfileForTest(t, ds, team.ID)
|
|
|
|
host := test.NewHost(t, ds, "deferred-recon-host", "1.1.1.1", "deferred-recon-key", "deferred-recon-host-uuid", time.Now(),
|
|
test.WithPlatform("windows"), test.WithTeamID(team.ID))
|
|
|
|
dev := &fleet.MDMWindowsEnrolledDevice{
|
|
MDMDeviceID: uuid.New().String(),
|
|
MDMHardwareID: uuid.New().String() + uuid.New().String(),
|
|
MDMDeviceState: microsoft_mdm.MDMDeviceStateEnrolled,
|
|
MDMDeviceType: "CIMClient_Windows",
|
|
MDMDeviceName: "TestDeviceName",
|
|
MDMEnrollType: "ProgrammaticEnrollment",
|
|
MDMEnrollProtoVersion: "5.0",
|
|
MDMEnrollClientVersion: "10.0.19045.2965",
|
|
MDMNotInOOBE: false,
|
|
HostUUID: host.UUID,
|
|
}
|
|
require.NoError(t, ds.MDMWindowsInsertEnrolledDevice(ctx, dev))
|
|
|
|
// Step 1: BulkSet defers Windows reconciliation. Production leaves
|
|
// updates.WindowsConfigProfile false (the consumer in
|
|
// service/mdm.go's BatchSetMDMProfiles ORs it with the transactional
|
|
// signal from batchSetMDMWindowsProfilesDB).
|
|
updates, err := ds.BulkSetPendingMDMHostProfiles(ctx, []uint{host.ID}, nil, nil, nil)
|
|
require.NoError(t, err)
|
|
assert.False(t, updates.WindowsConfigProfile)
|
|
|
|
rowsBefore, err := ds.GetHostMDMWindowsProfiles(ctx, host.UUID)
|
|
require.NoError(t, err)
|
|
require.Empty(t, rowsBefore, "host_mdm_windows_profiles must be untouched until the cron runs")
|
|
|
|
// Sanity: the listing must surface the host+profile pair, otherwise the
|
|
// cron has nothing to dispatch.
|
|
toInstall, err := ds.ListMDMWindowsProfilesToInstall(ctx)
|
|
require.NoError(t, err)
|
|
var matched bool
|
|
for _, p := range toInstall {
|
|
if p.HostUUID == host.UUID && p.ProfileUUID == profileUUID {
|
|
matched = true
|
|
break
|
|
}
|
|
}
|
|
require.True(t, matched, "desired-state listing did not surface our host+profile pair; got %d entries", len(toInstall))
|
|
|
|
// Step 2: drive the cron.
|
|
require.NoError(t, ReconcileWindowsProfiles(ctx, ds, logger))
|
|
|
|
// Step 3: the install row should now exist.
|
|
rowsAfter, err := ds.GetHostMDMWindowsProfiles(ctx, host.UUID)
|
|
require.NoError(t, err)
|
|
require.Len(t, rowsAfter, 1, "cron must enqueue the team's Windows profile for the new host")
|
|
assert.Equal(t, profileUUID, rowsAfter[0].ProfileUUID)
|
|
assert.Equal(t, fleet.MDMOperationTypeInstall, rowsAfter[0].OperationType)
|
|
}
|