mirror of
https://github.com/hashicorp/vault.git
synced 2025-11-28 22:21:30 +01:00
Handle Query Updates (#27547)
* Simplify handle query to return updated estimation values * Added changelog * remove comment * Revert "Simplify handle query to return updated estimation values" This reverts commit b67969ca170471698e2d96cdd99ed1478e3e0ea3. * temp * Revert "temp" This reverts commit 4932979b6d63debe5b07dabe983a7d575a94e981. * CE files handle query update * Revert "CE files handle query update" This reverts commit 8dafa2d03e63079906ef1485fe4a788e0596d1a6. * CE Changes * Delete vault/external_tests/upgrade_testing/upgrade_testing_binary/upgrade_test.go
This commit is contained in:
parent
054f5b182a
commit
0dc041625e
4
changelog/27547.txt
Normal file
4
changelog/27547.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
```release-note:improvement
|
||||||
|
activity log: Changes how new client counts in the current month are estimated, in order to return more
|
||||||
|
visibly sensible totals.
|
||||||
|
```
|
||||||
@ -1831,54 +1831,29 @@ func (a *ActivityLog) handleQuery(ctx context.Context, startTime, endTime time.T
|
|||||||
pq = storedQuery
|
pq = storedQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the namespace response breakdowns and totals for entities and tokens from the initial
|
|
||||||
// namespace data.
|
|
||||||
totalCounts, byNamespaceResponse, err := a.calculateByNamespaceResponseForQuery(ctx, pq.Namespaces)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we need to add the current month's client counts into the total, compute the namespace
|
|
||||||
// breakdown for the current month as well.
|
|
||||||
var partialByMonth map[int64]*processMonth
|
var partialByMonth map[int64]*processMonth
|
||||||
var partialByNamespace map[string]*processByNamespace
|
|
||||||
var byNamespaceResponseCurrent []*ResponseNamespace
|
|
||||||
var totalCurrentCounts *ResponseCounts
|
|
||||||
if computePartial {
|
if computePartial {
|
||||||
// Traverse through current month's activitylog data and group clients
|
// Traverse through current month's activitylog data and group clients
|
||||||
// into months and namespaces
|
// into months and namespaces
|
||||||
a.fragmentLock.RLock()
|
a.fragmentLock.RLock()
|
||||||
partialByMonth, partialByNamespace = a.populateNamespaceAndMonthlyBreakdowns()
|
partialByMonth, _ = a.populateNamespaceAndMonthlyBreakdowns()
|
||||||
a.fragmentLock.RUnlock()
|
a.fragmentLock.RUnlock()
|
||||||
|
|
||||||
// Convert the byNamespace breakdowns into structs that are
|
// Estimate the current month totals. These record contains is complete with all the
|
||||||
// consumable by the /activity endpoint, so as to reuse code between these two
|
// current month data, grouped by namespace and mounts
|
||||||
// endpoints.
|
currentMonth, err := a.computeCurrentMonthForBillingPeriod(ctx, partialByMonth, startTime, endTime)
|
||||||
byNamespaceComputation := a.transformALNamespaceBreakdowns(partialByNamespace)
|
|
||||||
|
|
||||||
// Calculate the namespace response breakdowns and totals for entities
|
|
||||||
// and tokens from current month namespace data.
|
|
||||||
totalCurrentCounts, byNamespaceResponseCurrent, err = a.calculateByNamespaceResponseForQuery(ctx, byNamespaceComputation)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a mapping of namespace id to slice index, so that we can efficiently update our results without
|
// Combine the existing months precomputed query with the current month data
|
||||||
// having to traverse the entire namespace response slice every time.
|
pq.CombineWithCurrentMonth(currentMonth)
|
||||||
nsrMap := make(map[string]int)
|
}
|
||||||
for i, nr := range byNamespaceResponse {
|
|
||||||
nsrMap[nr.NamespaceID] = i
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rather than blindly appending, which will create duplicates, check our existing counts against the current
|
// Convert the namespace data into a protobuf format that can be returned in the response
|
||||||
// month counts, and append or update as necessary. We also want to account for mounts and their counts.
|
totalCounts, byNamespaceResponse, err := a.calculateByNamespaceResponseForQuery(ctx, pq.Namespaces)
|
||||||
for _, nrc := range byNamespaceResponseCurrent {
|
if err != nil {
|
||||||
if ndx, ok := nsrMap[nrc.NamespaceID]; ok {
|
return nil, err
|
||||||
byNamespaceResponse[ndx].Add(nrc)
|
|
||||||
} else {
|
|
||||||
byNamespaceResponse = append(byNamespaceResponse, nrc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort clients within each namespace
|
// Sort clients within each namespace
|
||||||
@ -1888,34 +1863,6 @@ func (a *ActivityLog) handleQuery(ctx context.Context, startTime, endTime time.T
|
|||||||
totalCounts, byNamespaceResponse = a.limitNamespacesInALResponse(byNamespaceResponse, limitNamespaces)
|
totalCounts, byNamespaceResponse = a.limitNamespacesInALResponse(byNamespaceResponse, limitNamespaces)
|
||||||
}
|
}
|
||||||
|
|
||||||
distinctEntitiesResponse := totalCounts.EntityClients
|
|
||||||
if computePartial {
|
|
||||||
currentMonth, err := a.computeCurrentMonthForBillingPeriod(ctx, partialByMonth, startTime, endTime)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the namespace attribution for the current month to the newly computed current month value. Note
|
|
||||||
// that transformMonthBreakdowns calculates a superstruct of the required namespace struct due to its
|
|
||||||
// primary use-case being for precomputedQueryWorker, but we will reuse this code for brevity and extract
|
|
||||||
// the namespaces from it.
|
|
||||||
currentMonthNamespaceAttribution := a.transformMonthBreakdowns(partialByMonth)
|
|
||||||
|
|
||||||
// Ensure that there is only one element in this list -- if not, warn.
|
|
||||||
if len(currentMonthNamespaceAttribution) > 1 {
|
|
||||||
a.logger.Warn("more than one month worth of namespace and mount attribution calculated for "+
|
|
||||||
"current month values", "number of months", len(currentMonthNamespaceAttribution))
|
|
||||||
}
|
|
||||||
if len(currentMonthNamespaceAttribution) == 0 {
|
|
||||||
a.logger.Warn("no month data found, returning query with no namespace attribution for current month")
|
|
||||||
} else {
|
|
||||||
currentMonth.Namespaces = currentMonthNamespaceAttribution[0].Namespaces
|
|
||||||
currentMonth.NewClients.Namespaces = currentMonthNamespaceAttribution[0].NewClients.Namespaces
|
|
||||||
}
|
|
||||||
pq.Months = append(pq.Months, currentMonth)
|
|
||||||
distinctEntitiesResponse += pq.Months[len(pq.Months)-1].NewClients.Counts.EntityClients
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now populate the response based on breakdowns.
|
// Now populate the response based on breakdowns.
|
||||||
responseData := make(map[string]interface{})
|
responseData := make(map[string]interface{})
|
||||||
responseData["start_time"] = pq.StartTime.Format(time.RFC3339)
|
responseData["start_time"] = pq.StartTime.Format(time.RFC3339)
|
||||||
@ -1932,8 +1879,6 @@ func (a *ActivityLog) handleQuery(ctx context.Context, startTime, endTime time.T
|
|||||||
}
|
}
|
||||||
|
|
||||||
responseData["by_namespace"] = byNamespaceResponse
|
responseData["by_namespace"] = byNamespaceResponse
|
||||||
totalCounts.Add(totalCurrentCounts)
|
|
||||||
totalCounts.DistinctEntities = distinctEntitiesResponse
|
|
||||||
responseData["total"] = totalCounts
|
responseData["total"] = totalCounts
|
||||||
|
|
||||||
// Create and populate the month response structs based on the monthly breakdown.
|
// Create and populate the month response structs based on the monthly breakdown.
|
||||||
|
|||||||
@ -8,10 +8,13 @@ package activity_testonly
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/api"
|
"github.com/hashicorp/vault/api"
|
||||||
|
"github.com/hashicorp/vault/helper/namespace"
|
||||||
"github.com/hashicorp/vault/helper/testhelpers"
|
"github.com/hashicorp/vault/helper/testhelpers"
|
||||||
"github.com/hashicorp/vault/helper/testhelpers/minimal"
|
"github.com/hashicorp/vault/helper/testhelpers/minimal"
|
||||||
"github.com/hashicorp/vault/helper/timeutil"
|
"github.com/hashicorp/vault/helper/timeutil"
|
||||||
@ -447,3 +450,172 @@ func Test_ActivityLog_MountDeduplication(t *testing.T) {
|
|||||||
"secret/": 1,
|
"secret/": 1,
|
||||||
}, mountSet)
|
}, mountSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestHandleQuery_MultipleMounts creates a cluster with
|
||||||
|
// two userpass mounts. It then tests verifies that
|
||||||
|
// the total new counts are calculated within a reasonably level of accuracy for
|
||||||
|
// various numbers of clients in each mount.
|
||||||
|
func TestHandleQuery_MultipleMounts(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
twoMonthsAgo [][]int
|
||||||
|
oneMonthAgo [][]int
|
||||||
|
currentMonth [][]int
|
||||||
|
expectedNewClients int
|
||||||
|
expectedTotalAccuracy float64
|
||||||
|
}{
|
||||||
|
"low volume, all mounts": {
|
||||||
|
twoMonthsAgo: [][]int{
|
||||||
|
{20, 20},
|
||||||
|
},
|
||||||
|
oneMonthAgo: [][]int{
|
||||||
|
{30, 30},
|
||||||
|
},
|
||||||
|
currentMonth: [][]int{
|
||||||
|
{40, 40},
|
||||||
|
},
|
||||||
|
expectedNewClients: 80,
|
||||||
|
expectedTotalAccuracy: 1,
|
||||||
|
},
|
||||||
|
"medium volume, all mounts": {
|
||||||
|
twoMonthsAgo: [][]int{
|
||||||
|
{200, 200},
|
||||||
|
},
|
||||||
|
oneMonthAgo: [][]int{
|
||||||
|
{300, 300},
|
||||||
|
},
|
||||||
|
currentMonth: [][]int{
|
||||||
|
{400, 400},
|
||||||
|
},
|
||||||
|
expectedNewClients: 800,
|
||||||
|
expectedTotalAccuracy: 0.98,
|
||||||
|
},
|
||||||
|
"higher volume, all mounts": {
|
||||||
|
twoMonthsAgo: [][]int{
|
||||||
|
{200, 200},
|
||||||
|
},
|
||||||
|
oneMonthAgo: [][]int{
|
||||||
|
{300, 300},
|
||||||
|
},
|
||||||
|
currentMonth: [][]int{
|
||||||
|
{2000, 5000},
|
||||||
|
},
|
||||||
|
expectedNewClients: 7000,
|
||||||
|
expectedTotalAccuracy: 0.95,
|
||||||
|
},
|
||||||
|
"higher volume, no repeats": {
|
||||||
|
twoMonthsAgo: [][]int{
|
||||||
|
{200, 200},
|
||||||
|
},
|
||||||
|
oneMonthAgo: [][]int{
|
||||||
|
{300, 300},
|
||||||
|
},
|
||||||
|
currentMonth: [][]int{
|
||||||
|
{4000, 6000},
|
||||||
|
},
|
||||||
|
expectedNewClients: 10000,
|
||||||
|
expectedTotalAccuracy: 0.98,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
testname := fmt.Sprintf("%s", i)
|
||||||
|
t.Run(testname, func(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
cluster := minimal.NewTestSoloCluster(t, nil)
|
||||||
|
client := cluster.Cores[0].Client
|
||||||
|
_, err = client.Logical().Write("sys/internal/counters/config", map[string]interface{}{
|
||||||
|
"enabled": "enable",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Create two namespaces
|
||||||
|
namespaces := []string{namespace.RootNamespaceID}
|
||||||
|
mounts := make(map[string][]string)
|
||||||
|
|
||||||
|
// Add two userpass mounts to each namespace
|
||||||
|
for _, ns := range namespaces {
|
||||||
|
err = client.WithNamespace(ns).Sys().EnableAuthWithOptions("userpass1", &api.EnableAuthOptions{
|
||||||
|
Type: "userpass",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = client.WithNamespace(ns).Sys().EnableAuthWithOptions("userpass2", &api.EnableAuthOptions{
|
||||||
|
Type: "userpass",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
mounts[ns] = []string{"auth/userpass1", "auth/userpass2"}
|
||||||
|
}
|
||||||
|
|
||||||
|
activityLogGenerator := clientcountutil.NewActivityLogData(client)
|
||||||
|
|
||||||
|
// Write two months ago data
|
||||||
|
activityLogGenerator = activityLogGenerator.NewPreviousMonthData(2)
|
||||||
|
for nsIndex, nsId := range namespaces {
|
||||||
|
for mountIndex, mount := range mounts[nsId] {
|
||||||
|
activityLogGenerator = activityLogGenerator.
|
||||||
|
NewClientsSeen(tt.twoMonthsAgo[nsIndex][mountIndex], clientcountutil.WithClientNamespace(nsId), clientcountutil.WithClientMount(mount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write previous months data
|
||||||
|
activityLogGenerator = activityLogGenerator.NewPreviousMonthData(1)
|
||||||
|
for nsIndex, nsId := range namespaces {
|
||||||
|
for mountIndex, mount := range mounts[nsId] {
|
||||||
|
activityLogGenerator = activityLogGenerator.
|
||||||
|
NewClientsSeen(tt.oneMonthAgo[nsIndex][mountIndex], clientcountutil.WithClientNamespace(nsId), clientcountutil.WithClientMount(mount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write current month data
|
||||||
|
activityLogGenerator = activityLogGenerator.NewCurrentMonthData()
|
||||||
|
for nsIndex, nsPath := range namespaces {
|
||||||
|
for mountIndex, mount := range mounts[nsPath] {
|
||||||
|
activityLogGenerator = activityLogGenerator.
|
||||||
|
RepeatedClientSeen(clientcountutil.WithClientNamespace(nsPath), clientcountutil.WithClientMount(mount)).
|
||||||
|
NewClientsSeen(tt.currentMonth[nsIndex][mountIndex], clientcountutil.WithClientNamespace(nsPath), clientcountutil.WithClientMount(mount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write all the client count data
|
||||||
|
_, err = activityLogGenerator.Write(context.Background(), generation.WriteOptions_WRITE_PRECOMPUTED_QUERIES, generation.WriteOptions_WRITE_ENTITIES)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
endOfCurrentMonth := timeutil.EndOfMonth(time.Now().UTC())
|
||||||
|
|
||||||
|
// query activity log
|
||||||
|
resp, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||||
|
"end_time": {endOfCurrentMonth.Format(time.RFC3339)},
|
||||||
|
"start_time": {timeutil.StartOfMonth(timeutil.MonthsPreviousTo(2, time.Now().UTC())).Format(time.RFC3339)},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Ensure that the month response is the same as the totals, because all clients
|
||||||
|
// are new clients and there will be no approximation in the single month partial
|
||||||
|
// case
|
||||||
|
monthsRaw, ok := resp.Data["months"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("malformed results. got %v", resp.Data)
|
||||||
|
}
|
||||||
|
monthsResponse := make([]*vault.ResponseMonth, 0)
|
||||||
|
err = mapstructure.Decode(monthsRaw, &monthsResponse)
|
||||||
|
|
||||||
|
currentMonthClients := monthsResponse[len(monthsResponse)-1]
|
||||||
|
|
||||||
|
// Now verify that the new client totals for ALL namespaces are approximately accurate (there are no namespaces in CE)
|
||||||
|
newClientsError := math.Abs((float64)(currentMonthClients.NewClients.Counts.Clients - tt.expectedNewClients))
|
||||||
|
newClientsErrorMargin := newClientsError / (float64)(tt.expectedNewClients)
|
||||||
|
expectedAccuracyCalc := (1 - tt.expectedTotalAccuracy) * 100 / 100
|
||||||
|
if newClientsErrorMargin > expectedAccuracyCalc {
|
||||||
|
t.Fatalf("bad accuracy: expected %+v, found %+v", expectedAccuracyCalc, newClientsErrorMargin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the totals for the clients are visibly sensible (that is the total of all the individual new clients per namespace)
|
||||||
|
total := 0
|
||||||
|
for _, newClientCounts := range currentMonthClients.NewClients.Namespaces {
|
||||||
|
total += newClientCounts.Counts.Clients
|
||||||
|
}
|
||||||
|
if diff := math.Abs(float64(currentMonthClients.NewClients.Counts.Clients - total)); diff >= 1 {
|
||||||
|
t.Fatalf("total expected was %d but got %d", currentMonthClients.NewClients.Counts.Clients, total)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user