Skip to content

Commit 4932979

Browse files
committed
temp
1 parent 4f1d1eb commit 4932979

File tree

2 files changed

+203
-66
lines changed

2 files changed

+203
-66
lines changed

vault/activity_log.go

Lines changed: 11 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1830,54 +1830,29 @@ func (a *ActivityLog) handleQuery(ctx context.Context, startTime, endTime time.T
18301830
pq = storedQuery
18311831
}
18321832

1833-
// Calculate the namespace response breakdowns and totals for entities and tokens from the initial
1834-
// namespace data.
1835-
totalCounts, byNamespaceResponse, err := a.calculateByNamespaceResponseForQuery(ctx, pq.Namespaces)
1836-
if err != nil {
1837-
return nil, err
1838-
}
1839-
1840-
// If we need to add the current month's client counts into the total, compute the namespace
1841-
// breakdown for the current month as well.
18421833
var partialByMonth map[int64]*processMonth
1843-
var partialByNamespace map[string]*processByNamespace
1844-
var byNamespaceResponseCurrent []*ResponseNamespace
1845-
var totalCurrentCounts *ResponseCounts
18461834
if computePartial {
18471835
// Traverse through current month's activitylog data and group clients
18481836
// into months and namespaces
18491837
a.fragmentLock.RLock()
1850-
partialByMonth, partialByNamespace = a.populateNamespaceAndMonthlyBreakdowns()
1838+
partialByMonth, _ = a.populateNamespaceAndMonthlyBreakdowns()
18511839
a.fragmentLock.RUnlock()
18521840

1853-
// Convert the byNamespace breakdowns into structs that are
1854-
// consumable by the /activity endpoint, so as to reuse code between these two
1855-
// endpoints.
1856-
byNamespaceComputation := a.transformALNamespaceBreakdowns(partialByNamespace)
1857-
1858-
// Calculate the namespace response breakdowns and totals for entities
1859-
// and tokens from current month namespace data.
1860-
totalCurrentCounts, byNamespaceResponseCurrent, err = a.calculateByNamespaceResponseForQuery(ctx, byNamespaceComputation)
1841+
// Estimate the current month totals. These record contains is complete with all the
1842+
// current month data, grouped by namespace and mounts
1843+
currentMonth, err := a.computeCurrentMonthForBillingPeriod(ctx, partialByMonth, startTime, endTime)
18611844
if err != nil {
18621845
return nil, err
18631846
}
18641847

1865-
// Create a mapping of namespace id to slice index, so that we can efficiently update our results without
1866-
// having to traverse the entire namespace response slice every time.
1867-
nsrMap := make(map[string]int)
1868-
for i, nr := range byNamespaceResponse {
1869-
nsrMap[nr.NamespaceID] = i
1870-
}
1848+
// Combine the existing months precomputed query with the current month data
1849+
pq.CombineWithCurrentMonth(currentMonth)
1850+
}
18711851

1872-
// Rather than blindly appending, which will create duplicates, check our existing counts against the current
1873-
// month counts, and append or update as necessary. We also want to account for mounts and their counts.
1874-
for _, nrc := range byNamespaceResponseCurrent {
1875-
if ndx, ok := nsrMap[nrc.NamespaceID]; ok {
1876-
byNamespaceResponse[ndx].Add(nrc)
1877-
} else {
1878-
byNamespaceResponse = append(byNamespaceResponse, nrc)
1879-
}
1880-
}
1852+
// Convert the namespace data into a protobuf format that can be returned in the response
1853+
totalCounts, byNamespaceResponse, err := a.calculateByNamespaceResponseForQuery(ctx, pq.Namespaces)
1854+
if err != nil {
1855+
return nil, err
18811856
}
18821857

18831858
// Sort clients within each namespace
@@ -1887,34 +1862,6 @@ func (a *ActivityLog) handleQuery(ctx context.Context, startTime, endTime time.T
18871862
totalCounts, byNamespaceResponse = a.limitNamespacesInALResponse(byNamespaceResponse, limitNamespaces)
18881863
}
18891864

1890-
distinctEntitiesResponse := totalCounts.EntityClients
1891-
if computePartial {
1892-
currentMonth, err := a.computeCurrentMonthForBillingPeriod(ctx, partialByMonth, startTime, endTime)
1893-
if err != nil {
1894-
return nil, err
1895-
}
1896-
1897-
// Add the namespace attribution for the current month to the newly computed current month value. Note
1898-
// that transformMonthBreakdowns calculates a superstruct of the required namespace struct due to its
1899-
// primary use-case being for precomputedQueryWorker, but we will reuse this code for brevity and extract
1900-
// the namespaces from it.
1901-
currentMonthNamespaceAttribution := a.transformMonthBreakdowns(partialByMonth)
1902-
1903-
// Ensure that there is only one element in this list -- if not, warn.
1904-
if len(currentMonthNamespaceAttribution) > 1 {
1905-
a.logger.Warn("more than one month worth of namespace and mount attribution calculated for "+
1906-
"current month values", "number of months", len(currentMonthNamespaceAttribution))
1907-
}
1908-
if len(currentMonthNamespaceAttribution) == 0 {
1909-
a.logger.Warn("no month data found, returning query with no namespace attribution for current month")
1910-
} else {
1911-
currentMonth.Namespaces = currentMonthNamespaceAttribution[0].Namespaces
1912-
currentMonth.NewClients.Namespaces = currentMonthNamespaceAttribution[0].NewClients.Namespaces
1913-
}
1914-
pq.Months = append(pq.Months, currentMonth)
1915-
distinctEntitiesResponse += pq.Months[len(pq.Months)-1].NewClients.Counts.EntityClients
1916-
}
1917-
19181865
// Now populate the response based on breakdowns.
19191866
responseData := make(map[string]interface{})
19201867
responseData["start_time"] = pq.StartTime.Format(time.RFC3339)
@@ -1931,8 +1878,6 @@ func (a *ActivityLog) handleQuery(ctx context.Context, startTime, endTime time.T
19311878
}
19321879

19331880
responseData["by_namespace"] = byNamespaceResponse
1934-
totalCounts.Add(totalCurrentCounts)
1935-
totalCounts.DistinctEntities = distinctEntitiesResponse
19361881
responseData["total"] = totalCounts
19371882

19381883
// Create and populate the month response structs based on the monthly breakdown.

vault/activity_log_test.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"encoding/json"
1010
"errors"
1111
"fmt"
12+
"math"
1213
"net/http"
1314
"os"
1415
"path/filepath"
@@ -25,6 +26,7 @@ import (
2526
"github.com/go-test/deep"
2627
"github.com/golang/protobuf/proto"
2728
"github.com/hashicorp/go-uuid"
29+
"github.com/hashicorp/vault/builtin/credential/userpass"
2830
"github.com/hashicorp/vault/helper/constants"
2931
"github.com/hashicorp/vault/helper/namespace"
3032
"github.com/hashicorp/vault/helper/timeutil"
@@ -5084,3 +5086,193 @@ func TestActivityLog_reportPrecomputedQueryMetrics(t *testing.T) {
50845086
hasMetric(t, data, "identity.pki_acme.active.reporting_period", 3, nil)
50855087
})
50865088
}
5089+
5090+
// TestHandleQuery_MultipleMounts creates a cluster with
5091+
// two userpass mounts. It then tests verifies that
5092+
// the total new counts are calculated within a reasonably level of accuracy for
5093+
// various numbers of clients in each mount.
5094+
func TestHandleQuery_MultipleMounts(t *testing.T) {
5095+
tests := map[string]struct {
5096+
twoMonthsAgo [][]int
5097+
oneMonthAgo [][]int
5098+
currentMonth [][]int
5099+
expectedNewClients int
5100+
repeatPreviousMonth bool
5101+
expectedTotalAccuracy float64
5102+
}{
5103+
"low volume, all mounts": {
5104+
twoMonthsAgo: [][]int{
5105+
{20, 20},
5106+
},
5107+
oneMonthAgo: [][]int{
5108+
{30, 30},
5109+
},
5110+
currentMonth: [][]int{
5111+
{40, 40},
5112+
},
5113+
repeatPreviousMonth: true,
5114+
expectedNewClients: 80,
5115+
expectedTotalAccuracy: 1,
5116+
},
5117+
"medium volume, all mounts": {
5118+
twoMonthsAgo: [][]int{
5119+
{200, 200},
5120+
},
5121+
oneMonthAgo: [][]int{
5122+
{300, 300},
5123+
},
5124+
currentMonth: [][]int{
5125+
{400, 400},
5126+
},
5127+
repeatPreviousMonth: true,
5128+
expectedNewClients: 800,
5129+
expectedTotalAccuracy: 0.98,
5130+
},
5131+
"higher volume, all mounts": {
5132+
twoMonthsAgo: [][]int{
5133+
{200, 200},
5134+
},
5135+
oneMonthAgo: [][]int{
5136+
{300, 300},
5137+
},
5138+
currentMonth: [][]int{
5139+
{2000, 5000},
5140+
},
5141+
repeatPreviousMonth: true,
5142+
expectedNewClients: 7000,
5143+
expectedTotalAccuracy: 0.95,
5144+
},
5145+
"higher volume, no repeats": {
5146+
twoMonthsAgo: [][]int{
5147+
{200, 200},
5148+
},
5149+
oneMonthAgo: [][]int{
5150+
{300, 300},
5151+
},
5152+
currentMonth: [][]int{
5153+
{4000, 6000},
5154+
},
5155+
repeatPreviousMonth: false,
5156+
expectedNewClients: 10000,
5157+
expectedTotalAccuracy: 0.98,
5158+
},
5159+
}
5160+
5161+
for i, tt := range tests {
5162+
testname := fmt.Sprintf("%s", i)
5163+
t.Run(testname, func(t *testing.T) {
5164+
// Normalize to start of month to prevent end of month weirdness
5165+
startOfMonth := timeutil.StartOfMonth(time.Now().UTC())
5166+
5167+
storage := &logical.InmemStorage{}
5168+
coreConfig := &CoreConfig{
5169+
CredentialBackends: map[string]logical.Factory{
5170+
"userpass": userpass.Factory,
5171+
},
5172+
Physical: storage.Underlying(),
5173+
}
5174+
5175+
cluster := NewTestCluster(t, coreConfig, nil)
5176+
cluster.Start()
5177+
defer cluster.Cleanup()
5178+
core := cluster.Cores[0].Core
5179+
TestWaitActive(t, core)
5180+
5181+
a := core.activityLog
5182+
ctx := namespace.RootContext(nil)
5183+
var err error
5184+
5185+
namespaces := make([]*namespace.Namespace, 0, 6)
5186+
mounts := make(map[string][]*MountEntry)
5187+
ns := namespace.RootNamespace
5188+
namespaces = append(namespaces, ns)
5189+
5190+
// Add two userpass mounts to the root namespace
5191+
for i := 0; i < 1; i++ {
5192+
me1 := &MountEntry{
5193+
Table: credentialTableType,
5194+
Path: "up1/",
5195+
Type: "userpass",
5196+
}
5197+
err = core.enableCredential(namespace.ContextWithNamespace(ctx, namespaces[i]), me1)
5198+
require.NoError(t, err)
5199+
5200+
me2 := &MountEntry{
5201+
Table: credentialTableType,
5202+
Path: "up2/",
5203+
Type: "userpass",
5204+
}
5205+
err = core.enableCredential(namespace.ContextWithNamespace(ctx, namespaces[i]), me2)
5206+
require.NoError(t, err)
5207+
mounts[namespaces[i].ID] = []*MountEntry{me1, me2}
5208+
}
5209+
5210+
// Generate data for two months ago
5211+
clientPrefix := 1
5212+
var entityRecordsMonth2 []*activity.EntityRecord
5213+
for namespaceId, mountSlice := range mounts {
5214+
for mountIndex, mount := range mountSlice {
5215+
entityRecordsMonth2 = append(entityRecordsMonth2, generateClientData(0, tt.twoMonthsAgo[0][mountIndex], mount.Accessor, namespaceId, fmt.Sprintf("%d", clientPrefix))...)
5216+
}
5217+
}
5218+
5219+
// Generate data for a month ago
5220+
clientPrefix += 1
5221+
var entityRecordsMonth1 []*activity.EntityRecord
5222+
for namespaceId, mountSlice := range mounts {
5223+
for mountIndex, mount := range mountSlice {
5224+
entityRecordsMonth2 = append(entityRecordsMonth1, generateClientData(0, tt.oneMonthAgo[0][mountIndex], mount.Accessor, namespaceId, fmt.Sprintf("%d", clientPrefix))...)
5225+
}
5226+
}
5227+
5228+
startOfTwoMonthsAgo := timeutil.StartOfMonth(startOfMonth.AddDate(0, -2, 0)).UTC()
5229+
startOfOneMonthAgo := timeutil.StartOfMonth(startOfMonth.AddDate(0, -1, 0)).UTC()
5230+
generatePreviousMonthClientData(t, core, startOfTwoMonthsAgo, entityRecordsMonth2)
5231+
generatePreviousMonthClientData(t, core, startOfOneMonthAgo, entityRecordsMonth1)
5232+
5233+
// Generate the current months data
5234+
clientPrefix += 1
5235+
var currentMonthData []*activity.EntityRecord
5236+
for namespaceId, mountSlice := range mounts {
5237+
for mountIndex, mount := range mountSlice {
5238+
currentMonthData = append(currentMonthData, generateClientData(0, tt.currentMonth[0][mountIndex], mount.Accessor, namespaceId, fmt.Sprintf("%d", clientPrefix))...)
5239+
clientPrefix += 1
5240+
mountIndex++
5241+
}
5242+
}
5243+
generateCurrentMonthClientData(t, core, entityRecordsMonth2, currentMonthData)
5244+
5245+
endOfCurrentMonth := timeutil.EndOfMonth(time.Now().UTC())
5246+
actual, err := a.handleQuery(ctx, startOfTwoMonthsAgo, endOfCurrentMonth, 0)
5247+
5248+
// Ensure that the month response is the same as the totals, because all clients
5249+
// are new clients and there will be no approximation in the single month partial
5250+
// case
5251+
monthsRaw, ok := actual["months"]
5252+
if !ok {
5253+
t.Fatalf("malformed results. got %v", actual)
5254+
}
5255+
monthsResponse := make([]ResponseMonth, 0)
5256+
err = mapstructure.Decode(monthsRaw, &monthsResponse)
5257+
5258+
currentMonthClients := monthsResponse[len(monthsResponse)-1]
5259+
5260+
// New verify that the new client totals for ALL namespaces are approximately accurate
5261+
newClientsError := math.Abs((float64)(currentMonthClients.NewClients.Counts.Clients - tt.expectedNewClients))
5262+
newClientsErrorMargin := newClientsError / (float64)(tt.expectedNewClients)
5263+
expectedAccuracyCalc := (1 - tt.expectedTotalAccuracy) * 100 / 100
5264+
if newClientsErrorMargin > expectedAccuracyCalc {
5265+
t.Fatalf("bad accuracy: expected %+v, found %+v", expectedAccuracyCalc, newClientsErrorMargin)
5266+
}
5267+
5268+
// Verify that the totals for the clients are visibly sensible (that is the total of all the individual new clients per namespace)
5269+
total := 0
5270+
for _, newClientCounts := range currentMonthClients.NewClients.Namespaces {
5271+
total += newClientCounts.Counts.Clients
5272+
}
5273+
if diff := math.Abs(float64(currentMonthClients.NewClients.Counts.Clients - total)); diff >= 1 {
5274+
t.Fatalf("total expected was %d but got %d", currentMonthClients.NewClients.Counts.Clients, total)
5275+
}
5276+
})
5277+
}
5278+
}

0 commit comments

Comments
 (0)