Cache policy counts (#15244)

This commit is contained in:
Tim Lee
2023-11-21 12:52:06 -07:00
committed by GitHub
parent 0557f10ac5
commit c5ea7eac4f
9 changed files with 361 additions and 97 deletions
+2
View File
@@ -0,0 +1,2 @@
- policy results are now cached in mysql for faster sort operations on policy counts. counts are
updated by the cleanups_then_aggregation cron job 1X per hour by default.
+6
View File
@@ -780,6 +780,12 @@ func newCleanupsAndAggregationSchedule(
return ds.UpdateQueryAggregatedStats(ctx)
},
),
schedule.WithJob(
"policy_aggregated_stats",
func(ctx context.Context) error {
return ds.UpdateHostPolicyCounts(ctx)
},
),
schedule.WithJob(
"aggregated_munki_and_mdm",
func(ctx context.Context) error {
@@ -0,0 +1,45 @@
package tables
import (
"database/sql"
"fmt"
)
func init() {
MigrationClient.AddMigration(Up_20231121054530, Down_20231121054530)
}
func Up_20231121054530(tx *sql.Tx) error {
stmt := `
CREATE TABLE policy_stats (
id int(10) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
policy_id int(10) unsigned NOT NULL,
-- inherited_team_id is used to indicate the row contains inherited
-- global policies counts for a team, otherwise it will be 0. This allows us
-- to use the UNIQUE KEY constraint with this column to avoid duplicate rows
-- when policies.team_id is null.
-- A foreign key constraint is not used here because team 0 is not a valid team
inherited_team_id int(10) unsigned NOT NULL DEFAULT 0,
-- cached counts for the policy / team
passing_host_count MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
failing_host_count MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE,
UNIQUE KEY policy_team_unique (policy_id, inherited_team_id)
);
`
_, err := tx.Exec(stmt)
if err != nil {
return fmt.Errorf("failed to create policy_stats table: %w", err)
}
return nil
}
func Down_20231121054530(tx *sql.Tx) error {
return nil
}
@@ -0,0 +1,83 @@
package tables
import (
"database/sql"
"testing"
"github.com/stretchr/testify/require"
)
func TestUp_20231121054530(t *testing.T) {
db := applyUpToPrev(t)
const (
insertUsersStmt = `INSERT INTO users (id, name, password, salt, email) VALUES (?, ?, ?, ?, ?)`
insertPolicyStmt = `INSERT INTO policies (
team_id, name, query, description, author_id, platforms, critical
) VALUES (?, ?, ?, ?, ?, ?, ?)`
insertTeamStmt = `INSERT INTO teams (name) VALUES (?)`
deletePolicyStmt = `DELETE FROM policies WHERE id = ?`
loadPolicyStatsStmt = `SELECT
id, policy_id, inherited_team_id, passing_host_count, failing_host_count
FROM policy_stats WHERE id = ?`
)
// Apply current migration.
applyNext(t, db)
// Create a user
_, err := db.Exec(insertUsersStmt, 1, "user1", "password999", "salt999", "foo")
require.NoError(t, err)
// Create a team
res, err := db.Exec(insertTeamStmt, "team1")
require.NoError(t, err)
teamID, _ := res.LastInsertId()
// Create a global policy
res, err = db.Exec(insertPolicyStmt, nil, "global-policy", "SELECT 1;", "Global policy description", 1, "all", false)
require.NoError(t, err)
globalPolicyStatID, _ := res.LastInsertId()
// Insert a policy_stats entry for the global policy (globally)
_, err = db.Exec(`INSERT INTO policy_stats (policy_id, inherited_team_id, passing_host_count, failing_host_count) VALUES (?, 0, ?, ?)`, globalPolicyStatID, 100, 10)
require.NoError(t, err)
// Insert a policy_stats entry for the team inheriting the global policy
_, err = db.Exec(`INSERT INTO policy_stats (policy_id, inherited_team_id, passing_host_count, failing_host_count) VALUES (?, ?, ?, ?)`, globalPolicyStatID, teamID, 50, 5)
require.NoError(t, err)
// Verify the entries in the policy_stats table
var id int
var policyID int64
var inheritedTeamID int64
var passingCount, failingCount int
// Verify global policy stats (global level)
err = db.QueryRow(loadPolicyStatsStmt, 1).Scan(&id, &policyID, &inheritedTeamID, &passingCount, &failingCount)
require.NoError(t, err)
require.Equal(t, globalPolicyStatID, policyID)
require.Equal(t, int64(0), inheritedTeamID)
// Verify global policy stats (team level)
err = db.QueryRow(loadPolicyStatsStmt, 2).Scan(&id, &policyID, &inheritedTeamID, &passingCount, &failingCount)
require.NoError(t, err)
require.Equal(t, globalPolicyStatID, policyID)
require.Equal(t, teamID, inheritedTeamID)
// Verify global policy stats still exist (global level)
err = db.QueryRow(loadPolicyStatsStmt, globalPolicyStatID).Scan(&id, &policyID, &inheritedTeamID, &passingCount, &failingCount)
require.NoError(t, err)
// Delete the global policy and check that its policy_stats entry is also deleted
_, err = db.Exec(deletePolicyStmt, globalPolicyStatID)
require.NoError(t, err)
err = db.QueryRow(loadPolicyStatsStmt, globalPolicyStatID).Scan(&id, &policyID, &inheritedTeamID, &passingCount, &failingCount)
require.Error(t, err)
require.ErrorIs(t, err, sql.ErrNoRows)
}
+120 -95
View File
@@ -62,10 +62,13 @@ func (ds *Datastore) PolicyByName(ctx context.Context, name string) (*fleet.Poli
fmt.Sprint(`SELECT `+policyCols+`,
COALESCE(u.name, '<deleted>') AS author_name,
COALESCE(u.email, '') AS author_email,
(select count(*) from policy_membership where policy_id=p.id and passes=true) as passing_host_count,
(select count(*) from policy_membership where policy_id=p.id and passes=false) as failing_host_count
COALESCE(ps.passing_host_count, 0) as passing_host_count,
COALESCE(ps.failing_host_count, 0) as failing_host_count
FROM policies p
LEFT JOIN users u ON p.author_id = u.id
LEFT JOIN policy_stats ps ON p.id = ps.policy_id
AND ((p.team_id IS NULL AND ps.inherited_team_id = 0)
OR (p.team_id IS NOT NULL AND ps.inherited_team_id = p.team_id))
WHERE p.name=?`), name)
if err != nil {
if err == sql.ErrNoRows {
@@ -87,14 +90,17 @@ func policyDB(ctx context.Context, q sqlx.QueryerContext, id uint, teamID *uint)
var policy fleet.Policy
err := sqlx.GetContext(ctx, q, &policy,
fmt.Sprintf(`
SELECT `+policyCols+`,
SELECT %s,
COALESCE(u.name, '<deleted>') AS author_name,
COALESCE(u.email, '') AS author_email,
(select count(*) from policy_membership where policy_id=p.id and passes=true) as passing_host_count,
(select count(*) from policy_membership where policy_id=p.id and passes=false) as failing_host_count
COALESCE(ps.passing_host_count, 0) as passing_host_count,
COALESCE(ps.failing_host_count, 0) as failing_host_count
FROM policies p
LEFT JOIN users u ON p.author_id = u.id
WHERE p.id=? AND %s`, teamWhere),
LEFT JOIN policy_stats ps ON p.id = ps.policy_id
AND ((p.team_id IS NULL AND ps.inherited_team_id = 0)
OR (p.team_id IS NOT NULL AND ps.inherited_team_id = p.team_id))
WHERE p.id=? AND %s`, policyCols, teamWhere),
args...)
if err != nil {
if err == sql.ErrNoRows {
@@ -292,112 +298,75 @@ func (ds *Datastore) RecordPolicyQueryExecutions(ctx context.Context, host *flee
}
func (ds *Datastore) ListGlobalPolicies(ctx context.Context, opts fleet.ListOptions) ([]*fleet.Policy, error) {
return listPoliciesDB(ctx, ds.reader(ctx), nil, nil, opts)
return listPoliciesDB(ctx, ds.reader(ctx), nil, opts)
}
// returns the list of policies associated with the provided teamID, or the
// global policies if teamID is nil. The pass/fail host counts are the totals
// regardless of hosts' team if countsForTeamID is nil, or the totals just for
// hosts that belong to the provided countsForTeamID if it is not nil.
func listPoliciesDB(ctx context.Context, q sqlx.QueryerContext, teamID, countsForTeamID *uint, opts fleet.ListOptions) ([]*fleet.Policy, error) {
func listPoliciesDB(ctx context.Context, q sqlx.QueryerContext, teamID *uint, opts fleet.ListOptions) ([]*fleet.Policy, error) {
var args []interface{}
var initialQuery string
// Sorting by failing host counts requires an expensive join with the
// policy membership table and may result in long response times
if opts.OrderKey == "failing_host_count" {
if countsForTeamID != nil {
initialQuery = `
SELECT p.id
FROM policies p
LEFT JOIN (
SELECT pm.policy_id,
COUNT(*) AS failing_host_count
FROM policy_membership pm
INNER JOIN hosts h ON pm.host_id = h.id AND pm.passes = false AND h.team_id = ?
GROUP BY pm.policy_id
) AS subq ON p.id = subq.policy_id
`
args = append(args, *countsForTeamID)
} else {
initialQuery = `
SELECT p.id
FROM policies p
LEFT JOIN (
SELECT pm.policy_id,
COUNT(*) AS failing_host_count
FROM policy_membership pm
WHERE pm.passes = false
GROUP BY pm.policy_id
) AS subq ON p.id = subq.policy_id
`
}
} else {
initialQuery = "SELECT id FROM policies"
}
if teamID != nil {
initialQuery += " WHERE team_id = ?"
args = append(args, *teamID)
} else {
initialQuery += " WHERE team_id IS NULL"
}
initialQuery, args = searchLike(initialQuery, args, opts.MatchQuery, policySearchColumns...)
initialQuery, args = appendListOptionsWithCursorToSQL(initialQuery, args, &opts)
var ids []uint
err := sqlx.SelectContext(ctx, q, &ids, initialQuery, args...)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "retrieving policy ids")
}
if len(ids) == 0 {
return []*fleet.Policy{}, nil
}
args = []interface{}{} // reset args
counts := `
(select count(*) from policy_membership where policy_id=p.id and passes=true) as passing_host_count,
(select count(*) from policy_membership where policy_id=p.id and passes=false) as failing_host_count
`
if countsForTeamID != nil {
counts = `
(select count(*) from policy_membership pm inner join hosts h on pm.host_id = h.id where pm.policy_id=p.id and pm.passes=true and h.team_id = ?) as passing_host_count,
(select count(*) from policy_membership pm inner join hosts h on pm.host_id = h.id where pm.policy_id=p.id and pm.passes=false and h.team_id = ?) as failing_host_count
`
args = append(args, *countsForTeamID, *countsForTeamID)
}
query := fmt.Sprintf(`
SELECT `+policyCols+`,
query := `
SELECT ` + policyCols + `,
COALESCE(u.name, '<deleted>') AS author_name,
COALESCE(u.email, '') AS author_email,
%s
COALESCE(ps.passing_host_count, 0) AS passing_host_count,
COALESCE(ps.failing_host_count, 0) AS failing_host_count
FROM policies p
LEFT JOIN users u ON p.author_id = u.id
WHERE p.id IN (?)`, counts)
LEFT JOIN policy_stats ps ON p.id = ps.policy_id AND ps.inherited_team_id = 0
`
args = append(args, ids)
query, args, err = sqlx.In(query, args...)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "building query to get policies by ID")
if teamID != nil {
query += " WHERE team_id = ?"
args = append(args, *teamID)
} else {
query += " WHERE team_id IS NULL"
}
// removing pagination options to avoid double pagination
opts.Page = 0
opts.PerPage = 0
query, args = searchLike(query, args, opts.MatchQuery, policySearchColumns...)
query, args = appendListOptionsWithCursorToSQL(query, args, &opts)
var policies []*fleet.Policy
err = sqlx.SelectContext(ctx, q, &policies, query, args...)
err := sqlx.SelectContext(ctx, q, &policies, query, args...)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "listing policies")
}
return policies, nil
}
// getInheritedPoliciesForTeam returns the list of global policies with the
// passing and failing host counts for the provided teamID
func getInheritedPoliciesForTeam(ctx context.Context, q sqlx.QueryerContext, TeamID uint, opts fleet.ListOptions) ([]*fleet.Policy, error) {
var args []interface{}
query := `
SELECT
` + policyCols + `,
COALESCE(u.name, '<deleted>') AS author_name,
COALESCE(u.email, '') AS author_email,
COALESCE(ps.passing_host_count, 0) as passing_host_count,
COALESCE(ps.failing_host_count, 0) as failing_host_count
FROM policies p
LEFT JOIN users u ON p.author_id = u.id
LEFT JOIN policy_stats ps ON p.id = ps.policy_id AND ps.inherited_team_id = ?
WHERE p.team_id IS NULL
`
args = append(args, TeamID)
query, args = searchLike(query, args, opts.MatchQuery, policySearchColumns...)
query, _ = appendListOptionsToSQL(query, &opts)
var policies []*fleet.Policy
err := sqlx.SelectContext(ctx, q, &policies, query, args...)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "listing inherited policies")
}
return policies, nil
}
@@ -431,10 +400,13 @@ func (ds *Datastore) PoliciesByID(ctx context.Context, ids []uint) (map[uint]*fl
sql := `SELECT ` + policyCols + `,
COALESCE(u.name, '<deleted>') AS author_name,
COALESCE(u.email, '') AS author_email,
(select count(*) from policy_membership where policy_id=p.id and passes=true) as passing_host_count,
(select count(*) from policy_membership where policy_id=p.id and passes=false) as failing_host_count
COALESCE(ps.passing_host_count, 0) as passing_host_count,
COALESCE(ps.failing_host_count, 0) as failing_host_count
FROM policies p
LEFT JOIN users u ON p.author_id = u.id
LEFT JOIN policy_stats ps ON p.id = ps.policy_id
AND ((p.team_id IS NULL AND ps.inherited_team_id = 0)
OR (p.team_id IS NOT NULL AND ps.inherited_team_id = p.team_id))
WHERE p.id IN (?)`
query, args, err := sqlx.In(sql, ids)
if err != nil {
@@ -561,12 +533,12 @@ func (ds *Datastore) NewTeamPolicy(ctx context.Context, teamID uint, authorID *u
}
func (ds *Datastore) ListTeamPolicies(ctx context.Context, teamID uint, opts fleet.ListOptions, iopts fleet.ListOptions) (teamPolicies, inheritedPolicies []*fleet.Policy, err error) {
teamPolicies, err = listPoliciesDB(ctx, ds.reader(ctx), &teamID, nil, opts)
teamPolicies, err = listPoliciesDB(ctx, ds.reader(ctx), &teamID, opts)
if err != nil {
return nil, nil, err
}
// get inherited (global) policies with counts of hosts for that team
inheritedPolicies, err = listPoliciesDB(ctx, ds.reader(ctx), nil, &teamID, iopts)
inheritedPolicies, err = getInheritedPoliciesForTeam(ctx, ds.reader(ctx), teamID, iopts)
if err != nil {
return nil, nil, err
}
@@ -1133,3 +1105,56 @@ func amountPolicyViolationDaysDB(ctx context.Context, tx sqlx.QueryerContext) (i
return int(counts.FailingHostCount), int(counts.TotalHostCount), nil
}
func (ds *Datastore) UpdateHostPolicyCounts(ctx context.Context) error {
// Update Counts for Inherited Global Policies for each Team
_, err := ds.writer(ctx).ExecContext(ctx, `
INSERT INTO policy_stats (policy_id, inherited_team_id, passing_host_count, failing_host_count)
SELECT
p.id,
t.id AS inherited_team_id,
(
SELECT COUNT(*)
FROM policy_membership pm
INNER JOIN hosts h ON pm.host_id = h.id
WHERE pm.policy_id = p.id AND pm.passes = true AND h.team_id = t.id
) AS passing_host_count,
(
SELECT COUNT(*)
FROM policy_membership pm
INNER JOIN hosts h ON pm.host_id = h.id
WHERE pm.policy_id = p.id AND pm.passes = false AND h.team_id = t.id
) AS failing_host_count
FROM policies p
CROSS JOIN teams t
WHERE p.team_id IS NULL
GROUP BY p.id, t.id
ON DUPLICATE KEY UPDATE
passing_host_count = VALUES(passing_host_count),
failing_host_count = VALUES(failing_host_count);
`)
if err != nil {
return ctxerr.Wrap(ctx, err, "update host policy counts for inherited global policies")
}
// Update Counts for Global and Team Policies
_, err = ds.writer(ctx).ExecContext(ctx, `
INSERT INTO policy_stats (policy_id, inherited_team_id, passing_host_count, failing_host_count)
SELECT
p.id,
0 AS inherited_team_id, -- using 0 to represent global scope
COALESCE(SUM(IF(pm.passes IS NULL, 0, pm.passes = 1)), 0),
COALESCE(SUM(IF(pm.passes IS NULL, 0, pm.passes = 0)), 0)
FROM policies p
LEFT JOIN policy_membership pm ON p.id = pm.policy_id
GROUP BY p.id
ON DUPLICATE KEY UPDATE
passing_host_count = VALUES(passing_host_count),
failing_host_count = VALUES(failing_host_count);
`)
if err != nil {
return ctxerr.Wrap(ctx, err, "update host policy counts for global and team policies")
}
return nil
}
+75
View File
@@ -53,6 +53,7 @@ func TestPolicies(t *testing.T) {
{"TestListGlobalPoliciesCanPaginate", testListGlobalPoliciesCanPaginate},
{"TestListTeamPoliciesCanPaginate", testListTeamPoliciesCanPaginate},
{"TestCountPolicies", testCountPolicies},
{"TestUpdatePolicyHostCounts", testUpdatePolicyHostCounts},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
@@ -277,6 +278,8 @@ func testPoliciesMembershipView(deferred bool, t *testing.T, ds *Datastore) {
require.NoError(t, ds.RecordPolicyQueryExecutions(ctx, host2, map[uint]*bool{p2.ID: nil}, time.Now(), deferred))
require.NoError(t, ds.UpdateHostPolicyCounts(ctx))
policies, err := ds.ListGlobalPolicies(ctx, fleet.ListOptions{})
require.NoError(t, err)
require.Len(t, policies, 2)
@@ -292,6 +295,8 @@ func testPoliciesMembershipView(deferred bool, t *testing.T, ds *Datastore) {
require.NoError(t, ds.RecordPolicyQueryExecutions(ctx, host1, map[uint]*bool{p.ID: ptr.Bool(false)}, time.Now(), deferred))
require.NoError(t, ds.RecordPolicyQueryExecutions(ctx, host2, map[uint]*bool{p2.ID: ptr.Bool(false)}, time.Now(), deferred))
require.NoError(t, ds.UpdateHostPolicyCounts(ctx))
policies, err = ds.ListGlobalPolicies(ctx, fleet.ListOptions{})
require.NoError(t, err)
require.Len(t, policies, 2)
@@ -349,6 +354,8 @@ func testPoliciesMembershipView(deferred bool, t *testing.T, ds *Datastore) {
require.NoError(t, ds.RecordPolicyQueryExecutions(ctx, host4, map[uint]*bool{t2pol.ID: ptr.Bool(false), t2pol2.ID: ptr.Bool(true), p.ID: ptr.Bool(false)}, time.Now(), deferred))
require.NoError(t, ds.RecordPolicyQueryExecutions(ctx, host5, map[uint]*bool{t2pol.ID: ptr.Bool(true), t2pol2.ID: ptr.Bool(true), p2.ID: ptr.Bool(true)}, time.Now(), deferred))
require.NoError(t, ds.UpdateHostPolicyCounts(ctx))
t1Pols, t1Inherited, err := ds.ListTeamPolicies(ctx, team1.ID, fleet.ListOptions{}, fleet.ListOptions{})
require.NoError(t, err)
require.Len(t, t1Pols, 1)
@@ -1028,6 +1035,11 @@ func testPoliciesByID(t *testing.T, ds *Datastore) {
user1 := test.NewUser(t, ds, "Alice", "alice@example.com", true)
policy1 := newTestPolicy(t, ds, user1, "policy1", "darwin", nil)
_ = newTestPolicy(t, ds, user1, "policy2", "darwin", nil)
host1 := newTestHostWithPlatform(t, ds, "host1", "darwin", nil)
require.NoError(t, ds.RecordPolicyQueryExecutions(context.Background(), host1, map[uint]*bool{policy1.ID: ptr.Bool(true)}, time.Now(), false))
require.NoError(t, ds.UpdateHostPolicyCounts(context.Background()))
policiesByID, err := ds.PoliciesByID(context.Background(), []uint{1, 2})
require.NoError(t, err)
assert.Equal(t, len(policiesByID), 2)
@@ -1035,6 +1047,7 @@ func testPoliciesByID(t *testing.T, ds *Datastore) {
assert.Equal(t, policiesByID[1].Name, policy1.Name)
assert.Equal(t, policiesByID[2].ID, uint(2))
assert.Equal(t, policiesByID[2].Name, "policy2")
assert.Equal(t, uint(1), policiesByID[1].PassingHostCount)
_, err = ds.PoliciesByID(context.Background(), []uint{1, 2, 3})
require.Error(t, err)
@@ -1100,7 +1113,11 @@ func testTeamPolicyTransfer(t *testing.T, ds *Datastore) {
require.NoError(t, ds.RecordPolicyQueryExecutions(ctx, host2, map[uint]*bool{team1Policy.ID: ptr.Bool(false), globalPolicy.ID: ptr.Bool(true)}, time.Now(), false))
require.NoError(t, ds.RecordPolicyQueryExecutions(ctx, host2, map[uint]*bool{team1Policy.ID: ptr.Bool(true), globalPolicy.ID: ptr.Bool(true)}, time.Now(), false))
require.NoError(t, ds.UpdateHostPolicyCounts(ctx))
checkPassingCount := func(tm1, tm1Inherited, tm2Inherited, global uint) {
t.Helper()
require.NoError(t, ds.UpdateHostPolicyCounts(ctx))
policies, inherited, err := ds.ListTeamPolicies(ctx, team1.ID, fleet.ListOptions{}, fleet.ListOptions{})
require.NoError(t, err)
require.Len(t, policies, 1)
@@ -2429,3 +2446,61 @@ func testCountPolicies(t *testing.T, ds *Datastore) {
require.NoError(t, err)
assert.Equal(t, 10, globalCount)
}
func testUpdatePolicyHostCounts(t *testing.T, ds *Datastore) {
// new policy
policy, err := ds.NewGlobalPolicy(context.Background(), nil, fleet.PolicyPayload{Name: "global policy 1"})
require.NoError(t, err)
team, err := ds.NewTeam(context.Background(), &fleet.Team{Name: "team1"})
require.NoError(t, err)
// create 4 team hosts
var teamHosts []*fleet.Host
for i := 0; i < 4; i++ {
h, err := ds.NewHost(context.Background(), &fleet.Host{OsqueryHostID: ptr.String(fmt.Sprintf("host%d", i)), NodeKey: ptr.String(fmt.Sprintf("host%d", i)), TeamID: &team.ID})
require.NoError(t, err)
teamHosts = append(teamHosts, h)
}
// create 4 global hosts
var globalHosts []*fleet.Host
for i := 4; i < 8; i++ {
h, err := ds.NewHost(context.Background(), &fleet.Host{OsqueryHostID: ptr.String(fmt.Sprintf("host%d", i)), NodeKey: ptr.String(fmt.Sprintf("host%d", i)), TeamID: nil})
require.NoError(t, err)
globalHosts = append(globalHosts, h)
}
// add policy responses
for _, h := range teamHosts {
res := map[uint]*bool{
policy.ID: ptr.Bool(true),
}
err = ds.RecordPolicyQueryExecutions(context.Background(), h, res, time.Now(), false)
require.NoError(t, err)
}
for _, h := range globalHosts {
res := map[uint]*bool{
policy.ID: ptr.Bool(true),
}
err = ds.RecordPolicyQueryExecutions(context.Background(), h, res, time.Now(), false)
require.NoError(t, err)
}
// check policy host counts before update
policy, err = ds.Policy(context.Background(), policy.ID)
require.NoError(t, err)
require.Equal(t, uint(0), policy.FailingHostCount)
require.Equal(t, uint(0), policy.PassingHostCount)
// update policy host counts
err = ds.UpdateHostPolicyCounts(context.Background())
require.NoError(t, err)
// check policy host counts
policy, err = ds.Policy(context.Background(), policy.ID)
require.NoError(t, err)
require.Equal(t, uint(0), policy.FailingHostCount)
require.Equal(t, uint(8), policy.PassingHostCount)
}
File diff suppressed because one or more lines are too long
+1
View File
@@ -529,6 +529,7 @@ type Datastore interface {
PoliciesByID(ctx context.Context, ids []uint) (map[uint]*Policy, error)
DeleteGlobalPolicies(ctx context.Context, ids []uint) ([]uint, error)
CountPolicies(ctx context.Context, teamID *uint, matchQuery string) (int, error)
UpdateHostPolicyCounts(ctx context.Context) error
PolicyQueriesForHost(ctx context.Context, host *Host) (map[string]string, error)
+12
View File
@@ -392,6 +392,8 @@ type DeleteGlobalPoliciesFunc func(ctx context.Context, ids []uint) ([]uint, err
type CountPoliciesFunc func(ctx context.Context, teamID *uint, matchQuery string) (int, error)
type UpdateHostPolicyCountsFunc func(ctx context.Context) error
type PolicyQueriesForHostFunc func(ctx context.Context, host *fleet.Host) (map[string]string, error)
type AsyncBatchInsertPolicyMembershipFunc func(ctx context.Context, batch []fleet.PolicyMembershipResult) error
@@ -1314,6 +1316,9 @@ type DataStore struct {
CountPoliciesFunc CountPoliciesFunc
CountPoliciesFuncInvoked bool
UpdateHostPolicyCountsFunc UpdateHostPolicyCountsFunc
UpdateHostPolicyCountsFuncInvoked bool
PolicyQueriesForHostFunc PolicyQueriesForHostFunc
PolicyQueriesForHostFuncInvoked bool
@@ -3166,6 +3171,13 @@ func (s *DataStore) CountPolicies(ctx context.Context, teamID *uint, matchQuery
return s.CountPoliciesFunc(ctx, teamID, matchQuery)
}
func (s *DataStore) UpdateHostPolicyCounts(ctx context.Context) error {
s.mu.Lock()
s.UpdateHostPolicyCountsFuncInvoked = true
s.mu.Unlock()
return s.UpdateHostPolicyCountsFunc(ctx)
}
func (s *DataStore) PolicyQueriesForHost(ctx context.Context, host *fleet.Host) (map[string]string, error) {
s.mu.Lock()
s.PolicyQueriesForHostFuncInvoked = true