Unified Queue: add DB migration for existing pending activities (#26413)

This commit is contained in:
Martin Angers
2025-02-19 08:29:14 -05:00
committed by GitHub
parent bf417665c3
commit 6049b3919f
5 changed files with 790 additions and 2 deletions
@@ -0,0 +1 @@
* Added a DB migration to migrate existing pending activities to the new unified queue.
@@ -0,0 +1,337 @@
package tables
import (
"database/sql"
"fmt"
)
func init() {
MigrationClient.AddMigration(Up_20250217093329, Down_20250217093329)
}
func Up_20250217093329(tx *sql.Tx) error {
// this migration inserts pending software installs, software uninstalls,
// VPP app installs and script executions in the upcoming_activities table
// (inserts them already marked as "activated" since they are ready to be
// processed). There is no ordering guarantee for those already-pending
// activities, but any new upcoming activity will follow the unified queue
// order.
if err := migrateSoftwareInstalls(tx); err != nil {
return err
}
if err := migrateSoftwareUninstalls(tx); err != nil {
return err
}
if err := migrateVPPInstalls(tx); err != nil {
return err
}
if err := migrateScriptExecs(tx); err != nil {
return err
}
return nil
}
func migrateSoftwareInstalls(tx *sql.Tx) error {
_, err := tx.Exec(`
INSERT INTO upcoming_activities
(
host_id,
priority,
user_id,
fleet_initiated,
activity_type,
execution_id,
payload,
activated_at
)
SELECT
hsi.host_id,
0,
hsi.user_id,
hsi.policy_id IS NOT NULL, -- true if fleet-initiated
'software_install',
hsi.execution_id,
JSON_OBJECT(
'self_service', hsi.self_service,
'installer_filename', hsi.installer_filename,
'version', hsi.version,
'software_title_name', hsi.software_title_name,
'user', (SELECT JSON_OBJECT('name', name, 'email', email, 'gravatar_url', gravatar_url) FROM users WHERE id = hsi.user_id)
),
hsi.created_at
FROM
host_software_installs hsi
LEFT OUTER JOIN upcoming_activities ua
ON hsi.execution_id = ua.execution_id
WHERE
ua.id IS NULL AND
hsi.status = 'pending_install' AND
hsi.host_deleted_at IS NULL
`)
if err != nil {
return fmt.Errorf("failed to insert pending software installs: %w", err)
}
_, err = tx.Exec(`
INSERT INTO software_install_upcoming_activities
(
upcoming_activity_id,
software_installer_id,
policy_id,
software_title_id,
created_at
)
SELECT
ua.id,
hsi.software_installer_id,
hsi.policy_id,
hsi.software_title_id,
hsi.created_at
FROM
upcoming_activities ua
INNER JOIN host_software_installs hsi
ON hsi.execution_id = ua.execution_id
LEFT OUTER JOIN software_install_upcoming_activities sia
ON sia.upcoming_activity_id = ua.id
WHERE
ua.activity_type = 'software_install' AND
hsi.status = 'pending_install' AND
hsi.host_deleted_at IS NULL AND
sia.upcoming_activity_id IS NULL
`)
if err != nil {
return fmt.Errorf("failed to insert pending software installs secondary table: %w", err)
}
return nil
}
func migrateSoftwareUninstalls(tx *sql.Tx) error {
_, err := tx.Exec(`
INSERT INTO upcoming_activities
(
host_id,
priority,
user_id,
fleet_initiated,
activity_type,
execution_id,
payload,
activated_at
)
SELECT
hsi.host_id,
0,
hsi.user_id,
hsi.policy_id IS NOT NULL, -- true if fleet-initiated
'software_uninstall',
hsi.execution_id,
JSON_OBJECT(
'installer_filename', '',
'version', 'unknown',
'software_title_name', hsi.software_title_name,
'user', (SELECT JSON_OBJECT('name', name, 'email', email, 'gravatar_url', gravatar_url) FROM users WHERE id = hsi.user_id)
),
hsi.created_at
FROM
host_software_installs hsi
LEFT OUTER JOIN upcoming_activities ua
ON hsi.execution_id = ua.execution_id
WHERE
ua.id IS NULL AND
hsi.status = 'pending_uninstall' AND
hsi.host_deleted_at IS NULL
`)
if err != nil {
return fmt.Errorf("failed to insert pending software uninstalls: %w", err)
}
_, err = tx.Exec(`
INSERT INTO software_install_upcoming_activities
(
upcoming_activity_id,
software_installer_id,
software_title_id,
created_at
)
SELECT
ua.id,
hsi.software_installer_id,
hsi.software_title_id,
hsi.created_at
FROM
upcoming_activities ua
INNER JOIN host_software_installs hsi
ON hsi.execution_id = ua.execution_id
LEFT OUTER JOIN software_install_upcoming_activities sia
ON sia.upcoming_activity_id = ua.id
WHERE
ua.activity_type = 'software_uninstall' AND
hsi.status = 'pending_uninstall' AND
hsi.host_deleted_at IS NULL AND
sia.upcoming_activity_id IS NULL
`)
if err != nil {
return fmt.Errorf("failed to insert pending software uninstalls secondary table: %w", err)
}
return nil
}
func migrateVPPInstalls(tx *sql.Tx) error {
_, err := tx.Exec(`
INSERT INTO upcoming_activities
(
host_id,
priority,
user_id,
fleet_initiated,
activity_type,
execution_id,
payload,
activated_at
)
SELECT
hvi.host_id,
0,
hvi.user_id,
hvi.policy_id IS NOT NULL, -- true if fleet-initiated
'vpp_app_install',
hvi.command_uuid,
JSON_OBJECT(
'self_service', hvi.self_service,
'associated_event_id', hvi.associated_event_id,
'user', (SELECT JSON_OBJECT('name', name, 'email', email, 'gravatar_url', gravatar_url) FROM users WHERE id = hvi.user_id)
),
COALESCE(hvi.created_at, NOW(6))
FROM
host_vpp_software_installs hvi
INNER JOIN
nano_view_queue nvq ON nvq.command_uuid = hvi.command_uuid
LEFT OUTER JOIN upcoming_activities ua
ON hvi.command_uuid = ua.execution_id
WHERE
ua.id IS NULL AND
nvq.status IS NULL AND
hvi.removed = 0
`)
if err != nil {
return fmt.Errorf("failed to insert pending vpp app installs: %w", err)
}
_, err = tx.Exec(`
INSERT INTO vpp_app_upcoming_activities
(
upcoming_activity_id,
adam_id,
platform,
policy_id,
created_at
)
SELECT
ua.id,
hvi.adam_id,
hvi.platform,
hvi.policy_id,
hvi.created_at
FROM
upcoming_activities ua
INNER JOIN host_vpp_software_installs hvi
ON hvi.command_uuid = ua.execution_id
INNER JOIN
nano_view_queue nvq ON nvq.command_uuid = hvi.command_uuid
LEFT OUTER JOIN vpp_app_upcoming_activities vaua
ON vaua.upcoming_activity_id = ua.id
WHERE
ua.activity_type = 'vpp_app_install' AND
hvi.removed = 0 AND
nvq.status IS NULL AND
vaua.upcoming_activity_id IS NULL
`)
if err != nil {
return fmt.Errorf("failed to insert pending vpp app installs secondary table: %w", err)
}
return nil
}
func migrateScriptExecs(tx *sql.Tx) error {
// we don't want to migrate software uninstall scripts (those are already
// covered by the software uninstalls), but we don't have anything special to
// do as we will automatically ignore them with the left join on
// upcoming_activities (and the fact that software uninstalls are processed
// before scripts), because the uninstall scripts have the same execution id
// as the corresponding software uninstall.
_, err := tx.Exec(`
INSERT INTO upcoming_activities
(
host_id,
priority,
user_id,
fleet_initiated,
activity_type,
execution_id,
payload,
activated_at
)
SELECT
hsr.host_id,
0,
hsr.user_id,
hsr.policy_id IS NOT NULL, -- true if fleet-initiated
'script',
hsr.execution_id,
JSON_OBJECT(
'sync_request', hsr.sync_request,
'is_internal', hsr.is_internal,
'user', (SELECT JSON_OBJECT('name', name, 'email', email, 'gravatar_url', gravatar_url) FROM users WHERE id = hsr.user_id)
),
hsr.created_at
FROM
host_script_results hsr
LEFT OUTER JOIN upcoming_activities ua
ON hsr.execution_id = ua.execution_id
WHERE
ua.id IS NULL AND
hsr.exit_code IS NULL AND -- script is pending execution
hsr.host_deleted_at IS NULL
`)
if err != nil {
return fmt.Errorf("failed to insert pending script executions: %w", err)
}
_, err = tx.Exec(`
INSERT INTO script_upcoming_activities
(
upcoming_activity_id,
script_id,
script_content_id,
policy_id,
setup_experience_script_id,
created_at
)
SELECT
ua.id,
hsr.script_id,
hsr.script_content_id,
hsr.policy_id,
hsr.setup_experience_script_id,
hsr.created_at
FROM
upcoming_activities ua
INNER JOIN host_script_results hsr
ON hsr.execution_id = ua.execution_id
LEFT OUTER JOIN script_upcoming_activities sua
ON sua.upcoming_activity_id = ua.id
WHERE
ua.activity_type = 'script' AND
hsr.exit_code IS NULL AND
hsr.host_deleted_at IS NULL AND
sua.upcoming_activity_id IS NULL
`)
if err != nil {
return fmt.Errorf("failed to insert pending script executions secondary table: %w", err)
}
return nil
}
func Down_20250217093329(tx *sql.Tx) error {
return nil
}
@@ -0,0 +1,332 @@
package tables
import (
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
func TestUp_20250217093329_None(t *testing.T) {
db := applyUpToPrev(t)
// Apply current migration.
applyNext(t, db)
assertRowCount(t, db, "upcoming_activities", 0)
}
func TestUp_20250217093329_Script(t *testing.T) {
db := applyUpToPrev(t)
hostID := insertHost(t, db, nil)
// create a script content
contentIDs := insertScriptContents(t, db, 1)
scriptContentID := contentIDs[0]
// insert a couple pending but one has host_deleted_at set, and a non-pending script
execIDPending, execIDDeleted, execIDDone := uuid.NewString(), uuid.NewString(), uuid.NewString()
execNoErr(t, db, `INSERT INTO host_script_results
(host_id, execution_id, output, script_content_id, host_deleted_at)
VALUES (?, ?, '', ?, ?)`, hostID, execIDPending, scriptContentID, nil)
execNoErr(t, db, `INSERT INTO host_script_results
(host_id, execution_id, output, script_content_id, host_deleted_at)
VALUES (?, ?, '', ?, ?)`, hostID, execIDDeleted, scriptContentID, time.Now())
execNoErr(t, db, `INSERT INTO host_script_results
(host_id, execution_id, output, script_content_id, exit_code)
VALUES (?, ?, '', ?, 0)`, hostID, execIDDone, scriptContentID)
// Apply current migration.
applyNext(t, db)
assertRowCount(t, db, "upcoming_activities", 1)
assertRowCount(t, db, "script_upcoming_activities", 1)
assertRowCount(t, db, "software_install_upcoming_activities", 0)
assertRowCount(t, db, "vpp_app_upcoming_activities", 0)
var execID string
err := db.Get(&execID, `SELECT execution_id FROM upcoming_activities`)
require.NoError(t, err)
require.Equal(t, execIDPending, execID)
var count int
err = db.Get(&count, `SELECT COUNT(*) FROM upcoming_activities WHERE activated_at IS NULL`)
require.NoError(t, err)
require.EqualValues(t, 0, count)
}
func TestUp_20250217093329_SoftwareInstall(t *testing.T) {
db := applyUpToPrev(t)
hostID := insertHost(t, db, nil)
installerIDs, _ := insertSoftwareInstallers(t, db, 1)
installerID := installerIDs[0]
// insert a few pending but one has host_deleted_at, uninstall or removed set, and a non-pending install
hsiStmt := `
INSERT INTO host_software_installs (
host_id, execution_id, software_installer_id, install_script_exit_code,
host_deleted_at, removed, uninstall
) VALUES (?, ?, ?, ?, ?, ?, ?)`
execIDPending, execIDDeleted, execIDUninstall, execIDRemoved, execIDFailed :=
uuid.NewString(), uuid.NewString(), uuid.NewString(), uuid.NewString(), uuid.NewString()
execNoErr(t, db, hsiStmt, hostID, execIDPending, installerID, nil, nil, false, false)
execNoErr(t, db, hsiStmt, hostID, execIDDeleted, installerID, nil, time.Now(), false, false)
execNoErr(t, db, hsiStmt, hostID, execIDUninstall, installerID, nil, nil, false, true)
execNoErr(t, db, hsiStmt, hostID, execIDRemoved, installerID, nil, nil, true, false)
execNoErr(t, db, hsiStmt, hostID, execIDFailed, installerID, 1, nil, false, false)
t.Log("exec IDs: ", execIDPending, execIDDeleted, execIDUninstall, execIDRemoved, execIDFailed)
applyNext(t, db)
assertRowCount(t, db, "upcoming_activities", 2)
assertRowCount(t, db, "software_install_upcoming_activities", 2)
assertRowCount(t, db, "vpp_app_upcoming_activities", 0)
assertRowCount(t, db, "script_upcoming_activities", 0)
var execIDs []string
err := db.Select(&execIDs, `SELECT execution_id FROM upcoming_activities`)
require.NoError(t, err)
// will add both the pending install and uninstall to upcoming, but not the
// host deleted entry, the removed and the failed install
require.ElementsMatch(t, []string{execIDPending, execIDUninstall}, execIDs)
var count int
err = db.Get(&count, `SELECT COUNT(*) FROM upcoming_activities WHERE activated_at IS NULL`)
require.NoError(t, err)
require.EqualValues(t, 0, count)
}
func TestUp_20250217093329_SoftwareUninstall(t *testing.T) {
db := applyUpToPrev(t)
hostID := insertHost(t, db, nil)
installerIDs, _ := insertSoftwareInstallers(t, db, 1)
installerID := installerIDs[0]
// insert a few pending but one has host_deleted_at, is an install or has
// removed set, and a non-pending uninstall
hsiStmt := `
INSERT INTO host_software_installs (
host_id, execution_id, software_installer_id, uninstall_script_exit_code,
host_deleted_at, removed, uninstall
) VALUES (?, ?, ?, ?, ?, ?, ?)`
execIDPending, execIDDeleted, execIDInstall, execIDRemoved, execIDFailed :=
uuid.NewString(), uuid.NewString(), uuid.NewString(), uuid.NewString(), uuid.NewString()
execNoErr(t, db, hsiStmt, hostID, execIDPending, installerID, nil, nil, false, true)
execNoErr(t, db, hsiStmt, hostID, execIDDeleted, installerID, nil, time.Now(), false, true)
execNoErr(t, db, hsiStmt, hostID, execIDInstall, installerID, nil, nil, false, false)
execNoErr(t, db, hsiStmt, hostID, execIDRemoved, installerID, nil, nil, true, true)
execNoErr(t, db, hsiStmt, hostID, execIDFailed, installerID, 1, nil, false, true)
t.Log("exec IDs: ", execIDPending, execIDDeleted, execIDInstall, execIDRemoved, execIDFailed)
applyNext(t, db)
assertRowCount(t, db, "upcoming_activities", 2)
assertRowCount(t, db, "software_install_upcoming_activities", 2)
assertRowCount(t, db, "vpp_app_upcoming_activities", 0)
assertRowCount(t, db, "script_upcoming_activities", 0)
var execIDs []string
err := db.Select(&execIDs, `SELECT execution_id FROM upcoming_activities`)
require.NoError(t, err)
// will add both the pending install and uninstall to upcoming, but not the
// host deleted entry, the removed and the failed uninstall
require.ElementsMatch(t, []string{execIDPending, execIDInstall}, execIDs)
var count int
err = db.Get(&count, `SELECT COUNT(*) FROM upcoming_activities WHERE activated_at IS NULL`)
require.NoError(t, err)
require.EqualValues(t, 0, count)
}
func TestUp_20250217093329_VPPInstall(t *testing.T) {
db := applyUpToPrev(t)
hostID := insertHost(t, db, nil)
hostUUID := "12345678-1234-1234-1234-123456789012"
adamIDs, _ := insertVPPApps(t, db, 1, "darwin")
adamID := adamIDs[0]
execNoErr(t, db, `INSERT INTO nano_devices (id, authenticate)
VALUES (?, ?)`, hostUUID, "auth")
execNoErr(t, db, `INSERT INTO nano_enrollments (id, device_id, type, topic, push_magic, token_hex, last_seen_at)
VALUES (?, ?, ?, ?, ?, ?, ?)`, hostUUID, hostUUID, "device", "topic", "magic", "hex", time.Now())
// create a few pending but one is removed, and a non-pending install
execIDPending, execIDRemoved, execIDDone := uuid.NewString(), uuid.NewString(), uuid.NewString()
execNoErr(t, db, `INSERT INTO host_vpp_software_installs
(host_id, adam_id, platform, command_uuid, removed) VALUES (?, ?, ?, ?, ?)`,
hostID, adamID, "darwin", execIDPending, false)
execNoErr(t, db, `INSERT INTO nano_commands (command_uuid, request_type, command) VALUES (?, ?, ?)`,
execIDPending, "InstallApplication", "<?xml")
execNoErr(t, db, `INSERT INTO nano_enrollment_queue (id, command_uuid) VALUES (?, ?)`,
hostUUID, execIDPending)
execNoErr(t, db, `INSERT INTO host_vpp_software_installs
(host_id, adam_id, platform, command_uuid, removed) VALUES (?, ?, ?, ?, ?)`,
hostID, adamID, "darwin", execIDRemoved, true)
execNoErr(t, db, `INSERT INTO nano_commands (command_uuid, request_type, command) VALUES (?, ?, ?)`,
execIDRemoved, "InstallApplication", "<?xml")
execNoErr(t, db, `INSERT INTO nano_enrollment_queue (id, command_uuid) VALUES (?, ?)`,
hostUUID, execIDRemoved)
execNoErr(t, db, `INSERT INTO host_vpp_software_installs
(host_id, adam_id, platform, command_uuid, removed) VALUES (?, ?, ?, ?, ?)`,
hostID, adamID, "darwin", execIDDone, false)
execNoErr(t, db, `INSERT INTO nano_commands (command_uuid, request_type, command) VALUES (?, ?, ?)`,
execIDDone, "InstallApplication", "<?xml")
execNoErr(t, db, `INSERT INTO nano_enrollment_queue (id, command_uuid) VALUES (?, ?)`,
hostUUID, execIDDone)
execNoErr(t, db, `INSERT INTO nano_command_results (id, command_uuid, status, result) VALUES (?, ?, ?, ?)`,
hostUUID, execIDDone, "Acknowledged", "<?xml")
applyNext(t, db)
assertRowCount(t, db, "upcoming_activities", 1)
assertRowCount(t, db, "vpp_app_upcoming_activities", 1)
assertRowCount(t, db, "software_install_upcoming_activities", 0)
assertRowCount(t, db, "script_upcoming_activities", 0)
var execIDs []string
err := db.Select(&execIDs, `SELECT execution_id FROM upcoming_activities`)
require.NoError(t, err)
require.ElementsMatch(t, []string{execIDPending}, execIDs)
var count int
err = db.Get(&count, `SELECT COUNT(*) FROM upcoming_activities WHERE activated_at IS NULL`)
require.NoError(t, err)
require.EqualValues(t, 0, count)
}
func TestUp_20250217093329_Load(t *testing.T) {
db := applyUpToPrev(t)
// create a 1000 hosts each for macOS, Windows and Linux
macIDs, winIDs, linuxIDs, idsToUUIDs := insertHosts(t, db, 1000, 1000, 1000)
// create 10 scripts
scriptContentIDs := insertScriptContents(t, db, 10)
// create 10 software installers/uninstallers
installerIDs, _ := insertSoftwareInstallers(t, db, 10)
// create 10 VPP apps
adamIDs, _ := insertVPPApps(t, db, 10, "darwin")
// for each host, create a pending script execution, software install, software
// uninstall, and for macOS hosts create a VPP app install.
var allExecIDs []string
perPlatformIDs := map[string][]uint{"darwin": macIDs, "windows": winIDs, "linux": linuxIDs}
for platform, hostIDs := range perPlatformIDs {
for i, hostID := range hostIDs {
// create the pending script
execID := uuid.NewString()
execNoErr(t, db, `INSERT INTO host_script_results
(host_id, execution_id, output, script_content_id)
VALUES (?, ?, '', ?)`, hostID, execID, scriptContentIDs[i%len(scriptContentIDs)])
allExecIDs = append(allExecIDs, execID)
// create the pending software install
execID = uuid.NewString()
execNoErr(t, db, `INSERT INTO host_software_installs
(host_id, execution_id, software_installer_id) VALUES (?, ?, ?)`,
hostID, execID, installerIDs[i%len(installerIDs)])
allExecIDs = append(allExecIDs, execID)
// create the pending software uninstall
execID = uuid.NewString()
execNoErr(t, db, `INSERT INTO host_software_installs
(host_id, execution_id, software_installer_id, uninstall) VALUES (?, ?, ?, 1)`,
hostID, execID, installerIDs[(i+1)%len(installerIDs)])
allExecIDs = append(allExecIDs, execID)
if platform == "darwin" {
execID = uuid.NewString()
execNoErr(t, db, `INSERT INTO host_vpp_software_installs
(host_id, adam_id, platform, command_uuid) VALUES (?, ?, ?, ?)`,
hostID, adamIDs[i%len(adamIDs)], "darwin", execID)
execNoErr(t, db, `INSERT INTO nano_commands (command_uuid, request_type, command) VALUES (?, ?, ?)`,
execID, "InstallApplication", "<?xml")
execNoErr(t, db, `INSERT INTO nano_enrollment_queue (id, command_uuid) VALUES (?, ?)`,
idsToUUIDs[hostID], execID)
allExecIDs = append(allExecIDs, execID)
}
}
}
applyNext(t, db)
assertRowCount(t, db, "host_vpp_software_installs", 1000)
assertRowCount(t, db, "host_script_results", 3000)
assertRowCount(t, db, "host_software_installs", 6000)
assertRowCount(t, db, "upcoming_activities", 10000)
assertRowCount(t, db, "vpp_app_upcoming_activities", 1000)
assertRowCount(t, db, "software_install_upcoming_activities", 6000)
assertRowCount(t, db, "script_upcoming_activities", 3000)
var execIDs []string
err := db.Select(&execIDs, `SELECT execution_id FROM upcoming_activities`)
require.NoError(t, err)
require.Len(t, execIDs, len(allExecIDs))
require.ElementsMatch(t, allExecIDs, execIDs)
var count int
err = db.Get(&count, `SELECT COUNT(*) FROM upcoming_activities WHERE activated_at IS NULL`)
require.NoError(t, err)
require.EqualValues(t, 0, count)
var stats []struct {
TargetID int `db:"target_id"`
TargetIDStr string `db:"target_id_str"`
Count int `db:"count"`
}
// sanity-check software installs
err = db.Select(&stats, `SELECT software_installer_id as target_id, COUNT(DISTINCT host_id) as count
FROM upcoming_activities ua INNER JOIN software_install_upcoming_activities siua
ON ua.id = siua.upcoming_activity_id
WHERE ua.activity_type = 'software_install'
GROUP BY software_installer_id`)
require.NoError(t, err)
require.Len(t, stats, 10)
for _, stat := range stats {
// each installer installs on 1/10th of the hosts
require.EqualValues(t, 300, stat.Count)
}
// sanity-check software uninstalls
stats = stats[:0]
err = db.Select(&stats, `SELECT software_installer_id as target_id, COUNT(DISTINCT host_id) as count
FROM upcoming_activities ua INNER JOIN software_install_upcoming_activities siua
ON ua.id = siua.upcoming_activity_id
WHERE ua.activity_type = 'software_uninstall'
GROUP BY software_installer_id`)
require.NoError(t, err)
require.Len(t, stats, 10)
for _, stat := range stats {
// each installer uninstalls on 1/10th of the hosts
require.EqualValues(t, 300, stat.Count)
}
// sanity-check scripts
stats = stats[:0]
err = db.Select(&stats, `SELECT script_content_id as target_id, COUNT(DISTINCT host_id) as count
FROM upcoming_activities ua INNER JOIN script_upcoming_activities sua
ON ua.id = sua.upcoming_activity_id
WHERE ua.activity_type = 'script'
GROUP BY script_content_id`)
require.NoError(t, err)
require.Len(t, stats, 10)
for _, stat := range stats {
// each script runs on 1/10th of the hosts
require.EqualValues(t, 300, stat.Count)
}
// sanity-check VPP apps
stats = stats[:0]
err = db.Select(&stats, `SELECT adam_id as target_id_str, COUNT(DISTINCT host_id) as count
FROM upcoming_activities ua INNER JOIN vpp_app_upcoming_activities vaua
ON ua.id = vaua.upcoming_activity_id
WHERE ua.activity_type = 'vpp_app_install'
GROUP BY adam_id`)
require.NoError(t, err)
require.Len(t, stats, 10)
for _, stat := range stats {
// each vpp app installs on 1/10th of the macOS hosts
require.EqualValues(t, 100, stat.Count)
}
}
@@ -25,6 +25,7 @@ import (
"testing"
"time"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -190,6 +191,123 @@ func insertHost(t *testing.T, db *sqlx.DB, teamID *uint) uint {
return uint(id) //nolint:gosec // dismiss G115
}
// insertHosts inserts the specified number of hosts per platform. Note that
// macOS hosts will have their enrollment information inserted as well in the
// nano tables. It returns the host IDs of each platform, and the map of IDs to
// host UUIDs.
func insertHosts(t *testing.T, db *sqlx.DB, numMacOS, numWin, numLinux int) (macIDs, winIDs, linuxIDs []uint, idsToUUIDs map[uint]string) {
const insertHostStmt = `
INSERT INTO hosts (
hostname, uuid, platform, osquery_version, os_version, build, platform_like, code_name,
cpu_type, cpu_subtype, cpu_brand, hardware_vendor, hardware_model, hardware_version,
hardware_serial, computer_name, team_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
perPlatformCounts := map[string]int{"darwin": numMacOS, "windows": numWin, "linux": numLinux}
perPlatformOS := map[string]string{"darwin": "macOS 15.1", "windows": "Windows 11", "linux": "Ubuntu 24.04"}
perPlatformIDs := map[string][]uint{"darwin": macIDs, "windows": winIDs, "linux": linuxIDs}
perPlatformUUIDs := make(map[string][]string)
idsToUUIDs = make(map[uint]string, numMacOS+numWin+numLinux)
for platform, count := range perPlatformCounts {
for i := 0; i < count; i++ {
// Insert a minimal record into hosts table
hostName := fmt.Sprintf("host-%s-%d", platform, i)
hostUUID := uuid.NewString()
hostPlatform := platform
osqueryVer := "5.9.1"
osVersion := perPlatformOS[platform]
buildVersion := "10.0.19042.1234"
platformLike := platform
codeName := "20H2"
cpuType := "x86_64"
cpuSubtype := "x86_64"
cpuBrand := "Intel"
hwVendor := "Dell Inc."
hwModel := "OptiPlex 7090"
hwVersion := "1.0"
hwSerial := uuid.NewString()
computerName := fmt.Sprintf("DESKTOP-%s-%d", platform, i)
id := execNoErrLastID(t, db, insertHostStmt, hostName, hostUUID, hostPlatform, osqueryVer,
osVersion, buildVersion, platformLike, codeName, cpuType, cpuSubtype, cpuBrand,
hwVendor, hwModel, hwVersion, hwSerial, computerName, nil)
perPlatformIDs[platform] = append(perPlatformIDs[platform], uint(id)) // nolint:gosec
perPlatformUUIDs[platform] = append(perPlatformUUIDs[platform], hostUUID)
idsToUUIDs[uint(id)] = hostUUID // nolint:gosec
}
}
for _, uid := range perPlatformUUIDs["darwin"] {
execNoErr(t, db, `INSERT INTO nano_devices (id, authenticate)
VALUES (?, ?)`, uid, "auth")
execNoErr(t, db, `INSERT INTO nano_enrollments (id, device_id, type, topic, push_magic, token_hex, last_seen_at)
VALUES (?, ?, ?, ?, ?, ?, ?)`, uid, uid, "device", "topic", "magic", "hex", time.Now())
}
return perPlatformIDs["darwin"], perPlatformIDs["windows"], perPlatformIDs["linux"], idsToUUIDs
}
func insertScriptContents(t *testing.T, db *sqlx.DB, count int) []uint {
ids := make([]uint, 0, count)
for i := 0; i < count; i++ {
content := fmt.Sprintf(`echo %d`, i)
csum := md5ChecksumScriptContent(content)
id := execNoErrLastID(t, db, `INSERT INTO script_contents
(md5_checksum, contents) VALUES (UNHEX(?), ?)`, csum, content)
ids = append(ids, uint(id)) //nolint:gosec
}
return ids
}
// returns the installer IDs and the title IDs
func insertSoftwareInstallers(t *testing.T, db *sqlx.DB, count int) (installerIDs, titleIDs []uint) {
installerIDs = make([]uint, 0, count)
titleIDs = make([]uint, 0, count)
for i := 0; i < count; i++ {
content := fmt.Sprintf(`install %d`, i)
csum := md5ChecksumScriptContent(content)
installID := execNoErrLastID(t, db, `INSERT INTO script_contents
(md5_checksum, contents) VALUES (UNHEX(?), ?)`, csum, content)
content = fmt.Sprintf(`uninstall %d`, i)
csum = md5ChecksumScriptContent(content)
uninstallID := execNoErrLastID(t, db, `INSERT INTO script_contents
(md5_checksum, contents) VALUES (UNHEX(?), ?)`, csum, content)
titleID := execNoErrLastID(t, db, `INSERT INTO software_titles
(name, source, browser) VALUES (?, 'apps', '')`, fmt.Sprintf("Foo%d.app", i))
installerID := execNoErrLastID(t, db, `INSERT INTO software_installers
(title_id, filename, version, platform, install_script_content_id, storage_id, package_ids, uninstall_script_content_id)
VALUES (?, ?, '1.1', 'darwin', ?, ?, '', ?)`, titleID, fmt.Sprintf("foo-%d.pkg", i), installID, fmt.Sprintf("storage-%d", i), uninstallID)
installerIDs = append(installerIDs, uint(installerID)) //nolint:gosec
titleIDs = append(titleIDs, uint(titleID)) //nolint:gosec
}
return installerIDs, titleIDs
}
func insertVPPApps(t *testing.T, db *sqlx.DB, count int, platform string) (adamIDs []string, titleIDs []uint) {
adamIDs = make([]string, 0, count)
titleIDs = make([]uint, 0, count)
for i := 0; i < count; i++ {
titleID := execNoErrLastID(t, db, `INSERT INTO software_titles
(name, source, browser) VALUES (?, 'apps', '')`, fmt.Sprintf("Bar%d.app", i))
adamID := fmt.Sprintf("adam-%d", i)
execNoErr(t, db, `INSERT INTO vpp_apps (adam_id, platform, title_id)
VALUES (?, ?, ?)`, adamID, platform, titleID)
adamIDs = append(adamIDs, adamID)
titleIDs = append(titleIDs, uint(titleID)) //nolint:gosec
}
return adamIDs, titleIDs
}
func assertRowCount(t *testing.T, db *sqlx.DB, table string, count int) {
var n int
err := db.Get(&n, fmt.Sprintf("SELECT COUNT(*) FROM %s", table))
File diff suppressed because one or more lines are too long