Batched selectSoftwareVersionsSQL (#27361)
https://github.com/fleetdm/fleet/issues/26753 Missed a spot in https://github.com/fleetdm/fleet/pull/27062. The next place in line that causes the placeholder overflow is `selectSoftwareVersionsSQL` - [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/Committing-Changes.md#changes-files) for more information. ^ changes are in the previous PR - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] Added/updated automated tests - [x] A detailed QA plan exists on the associated ticket (if it isn't there, work with the product group's QA engineer to add it) - [x] Manual QA for all new/changed functionality
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/datastore/mysql/common_mysql"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
@@ -221,18 +222,28 @@ func (ds *Datastore) ListSoftwareTitles(
|
||||
// the application logic. This is because we need to support MySQL 5.7
|
||||
// and there's no good way to do an aggregation that builds a structure
|
||||
// (like a JSON) object for nested arrays.
|
||||
getVersionsStmt, args, err := ds.selectSoftwareVersionsSQL(
|
||||
titleIDs,
|
||||
opt.TeamID,
|
||||
tmFilter,
|
||||
false,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, nil, ctxerr.Wrap(ctx, err, "build get versions stmt")
|
||||
}
|
||||
batchSize := 32000
|
||||
var versions []fleet.SoftwareVersion
|
||||
if err := sqlx.SelectContext(ctx, dbReader, &versions, getVersionsStmt, args...); err != nil {
|
||||
return nil, 0, nil, ctxerr.Wrap(ctx, err, "get software versions")
|
||||
err = common_mysql.BatchProcessSimple(titleIDs, batchSize, func(titleIDsToProcess []uint) error {
|
||||
getVersionsStmt, args, err := ds.selectSoftwareVersionsSQL(
|
||||
titleIDsToProcess,
|
||||
opt.TeamID,
|
||||
tmFilter,
|
||||
false,
|
||||
)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "build get versions stmt")
|
||||
}
|
||||
var versionsBatch []fleet.SoftwareVersion
|
||||
if err := sqlx.SelectContext(ctx, dbReader, &versions, getVersionsStmt, args...); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "get software versions")
|
||||
}
|
||||
versions = append(versions, versionsBatch...)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, 0, nil, err
|
||||
}
|
||||
|
||||
// append matching versions to titles
|
||||
|
||||
@@ -3,6 +3,7 @@ package mysql
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -28,6 +29,7 @@ func TestSoftwareTitles(t *testing.T) {
|
||||
{"TeamFilterSoftwareTitles", testTeamFilterSoftwareTitles},
|
||||
{"ListSoftwareTitlesInstallersOnly", testListSoftwareTitlesInstallersOnly},
|
||||
{"ListSoftwareTitlesAvailableForInstallFilter", testListSoftwareTitlesAvailableForInstallFilter},
|
||||
{"ListSoftwareTitlesOverflow", testListSoftwareTitlesOverflow},
|
||||
{"ListSoftwareTitlesAllTeams", testListSoftwareTitlesAllTeams},
|
||||
{"UploadedSoftwareExists", testUploadedSoftwareExists},
|
||||
{"ListSoftwareTitlesVulnerabilityFilters", testListSoftwareTitlesVulnerabilityFilters},
|
||||
@@ -1181,6 +1183,47 @@ func testListSoftwareTitlesAvailableForInstallFilter(t *testing.T, ds *Datastore
|
||||
}, names)
|
||||
}
|
||||
|
||||
func testListSoftwareTitlesOverflow(t *testing.T, ds *Datastore) {
|
||||
t.Skip("This test is too slow to run in CI")
|
||||
ctx := context.Background()
|
||||
|
||||
host := test.NewHost(t, ds, "host", "", "hostkey1", "hostuuid1", time.Now())
|
||||
host2 := test.NewHost(t, ds, "host2", "", "hostkey2", "hostuuid2", time.Now())
|
||||
|
||||
var software []fleet.Software
|
||||
for i := uint(0); i < 40_000; i++ {
|
||||
software = append(software,
|
||||
fleet.Software{Name: fmt.Sprintf("%dname", i), Version: fmt.Sprintf("0.0.%d", i), Source: "deb_packages"},
|
||||
)
|
||||
// UpdateHostSoftware blows up on a similar placeholder limit if we don't break it up
|
||||
if i == 20_000 {
|
||||
_, err := ds.UpdateHostSoftware(ctx, host.ID, software)
|
||||
require.NoError(t, err)
|
||||
software = []fleet.Software{}
|
||||
}
|
||||
if i == 39_999 {
|
||||
_, err := ds.UpdateHostSoftware(ctx, host2.ID, software)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
require.NoError(t, ds.SyncHostsSoftwareTitles(ctx, time.Now()))
|
||||
|
||||
_, counts, _, err := ds.ListSoftwareTitles(
|
||||
ctx,
|
||||
fleet.SoftwareTitleListOptions{
|
||||
ListOptions: fleet.ListOptions{
|
||||
OrderKey: "name",
|
||||
OrderDirection: fleet.OrderAscending,
|
||||
},
|
||||
TeamID: nil,
|
||||
},
|
||||
fleet.TeamFilter{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, 40000, counts)
|
||||
}
|
||||
|
||||
func testListSoftwareTitlesAllTeams(t *testing.T, ds *Datastore) {
|
||||
ctx := context.Background()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user