mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-03 20:40:45 -05:00
VAULT-34674 (#30164)
* apply oss changes * comment fix * fix TestOperatorUsageCommandRun by defining billing start via license * update go mod * revert the changes in operator usage test * fix operator usage test * fix acme regeneration tests * revert the changes for activity testonly test * fix activity testonly tests * seperate tests into ce and ent * move 2 more tests to oss and ent * remove left over test from common * updates after feedback * updates * added unit tests to tests oss get start and end time function * bring updates from ent * carry over updates from ent pr * fix the wording in ce warning * add a dot to ent warning * update comment * revert go mod and go sum changes, remove the unintended oss changes patch * add changelog entree for ce
This commit is contained in:
parent
a698cdee5e
commit
4f661c67c1
10 changed files with 765 additions and 558 deletions
3
changelog/30164.txt
Normal file
3
changelog/30164.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:change
|
||||
activity: provided value for `end_time` in `sys/internal/counters/activity` is now capped at the end of the last completed month.
|
||||
```
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
//go:build testonly
|
||||
//go:build testonly && !enterprise
|
||||
|
||||
package command_testonly
|
||||
|
||||
|
|
@ -54,12 +54,12 @@ func TestOperatorUsageCommandRun(t *testing.T) {
|
|||
now := time.Now().UTC()
|
||||
|
||||
_, err = clientcountutil.NewActivityLogData(client).
|
||||
NewPreviousMonthData(1).
|
||||
NewPreviousMonthData(2).
|
||||
NewClientsSeen(6, clientcountutil.WithClientType("entity")).
|
||||
NewClientsSeen(4, clientcountutil.WithClientType("non-entity-token")).
|
||||
NewClientsSeen(2, clientcountutil.WithClientType("secret-sync")).
|
||||
NewClientsSeen(7, clientcountutil.WithClientType("pki-acme")).
|
||||
NewCurrentMonthData().
|
||||
NewPreviousMonthData(1).
|
||||
NewClientsSeen(3, clientcountutil.WithClientType("entity")).
|
||||
NewClientsSeen(4, clientcountutil.WithClientType("non-entity-token")).
|
||||
NewClientsSeen(5, clientcountutil.WithClientType("secret-sync")).
|
||||
|
|
@ -70,8 +70,8 @@ func TestOperatorUsageCommandRun(t *testing.T) {
|
|||
ui, cmd := testOperatorUsageCommand(t)
|
||||
|
||||
t.Setenv("VAULT_TOKEN", client.Token())
|
||||
start := timeutil.MonthsPreviousTo(1, now).Format(time.RFC3339)
|
||||
end := timeutil.EndOfMonth(now).UTC().Format(time.RFC3339)
|
||||
start := timeutil.MonthsPreviousTo(2, now).Format(time.RFC3339)
|
||||
end := timeutil.EndOfMonth(timeutil.MonthsPreviousTo(1, now)).UTC().Format(time.RFC3339)
|
||||
// Reset and check output
|
||||
code := cmd.Run([]string{
|
||||
"-address", client.Address(),
|
||||
|
|
@ -2033,18 +2033,14 @@ func (a *ActivityLog) handleQuery(ctx context.Context, startTime, endTime time.T
|
|||
|
||||
// Now populate the response based on breakdowns.
|
||||
responseData := make(map[string]interface{})
|
||||
responseData["start_time"] = pq.StartTime.Format(time.RFC3339)
|
||||
responseData["start_time"] = startTime.Format(time.RFC3339)
|
||||
|
||||
// If we computed partial counts, we should return the actual end time we computed counts for, not the pre-computed
|
||||
// query end time. If we don't do this, the end_time in the response doesn't match the actual data in the response,
|
||||
// which is confusing. Note that regardless of what end time is given, if it falls within the current month, it will
|
||||
// be set to the end of the current month. This is definitely suboptimal, and possibly confusing, but still an
|
||||
// improvement over using the pre-computed query end time.
|
||||
if computePartial {
|
||||
responseData["end_time"] = endTime.Format(time.RFC3339)
|
||||
} else {
|
||||
responseData["end_time"] = pq.EndTime.Format(time.RFC3339)
|
||||
}
|
||||
responseData["end_time"] = endTime.Format(time.RFC3339)
|
||||
|
||||
responseData["by_namespace"] = byNamespaceResponse
|
||||
responseData["total"] = totalCounts
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
|
||||
"github.com/hashicorp/vault/helper/timeutil"
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
@ -36,3 +38,99 @@ func TestActivityLog_setupClientIDsUsageInfo_CE(t *testing.T) {
|
|||
|
||||
require.Len(t, a.GetClientIDsUsageInfo(), 0)
|
||||
}
|
||||
|
||||
// TestGetStartEndTime_EndTimeAdjustedToPastMonth tests getStartEndTime for proper adjustment of given end time to past month
|
||||
func TestGetStartEndTime_EndTimeAdjustedToPastMonth(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
currentMonthStart := timeutil.StartOfMonth(now)
|
||||
previousMonthEnd := timeutil.EndOfMonth(timeutil.MonthsPreviousTo(1, now))
|
||||
|
||||
// billing start time is zero for CE
|
||||
billingStartTime := time.Time{}
|
||||
sixMonthsAgo := now.AddDate(0, -6, 0)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
givenStartTime time.Time
|
||||
givenEndTime time.Time
|
||||
expectedStart time.Time
|
||||
expectedEnd time.Time
|
||||
expectWarning bool
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "End time in the past is unchanged",
|
||||
givenStartTime: sixMonthsAgo,
|
||||
givenEndTime: now.AddDate(0, -1, -1),
|
||||
expectedStart: sixMonthsAgo,
|
||||
expectedEnd: now.AddDate(0, -1, -1).UTC(),
|
||||
expectWarning: false,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "End time in the current month is clamped to previous month",
|
||||
givenStartTime: sixMonthsAgo,
|
||||
givenEndTime: currentMonthStart.AddDate(0, 0, 5).Add(2 * time.Hour),
|
||||
expectedStart: sixMonthsAgo,
|
||||
expectedEnd: previousMonthEnd,
|
||||
expectWarning: true,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "End time in the future is clamped to previous month",
|
||||
givenStartTime: sixMonthsAgo,
|
||||
givenEndTime: now.AddDate(0, 1, 0),
|
||||
expectedStart: sixMonthsAgo,
|
||||
expectedEnd: previousMonthEnd,
|
||||
expectWarning: true,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "End time is zero and gets clamped to previous month",
|
||||
givenStartTime: sixMonthsAgo,
|
||||
givenEndTime: time.Time{},
|
||||
expectedStart: sixMonthsAgo,
|
||||
expectedEnd: previousMonthEnd,
|
||||
expectWarning: true,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "Start time after end time causes error",
|
||||
givenStartTime: now.AddDate(0, 2, 0),
|
||||
givenEndTime: now.AddDate(0, 1, 0),
|
||||
expectWarning: false,
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
d := &framework.FieldData{
|
||||
Schema: map[string]*framework.FieldSchema{
|
||||
"start_time": {
|
||||
Type: framework.TypeTime,
|
||||
},
|
||||
"end_time": {
|
||||
Type: framework.TypeTime,
|
||||
},
|
||||
},
|
||||
Raw: map[string]any{
|
||||
"start_time": tc.givenStartTime.Format(time.RFC3339Nano),
|
||||
"end_time": tc.givenEndTime.Format(time.RFC3339Nano),
|
||||
},
|
||||
}
|
||||
|
||||
start, end, warnings, err := getStartEndTime(d, billingStartTime)
|
||||
|
||||
if tc.expectErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.WithinDuration(t, tc.expectedStart, start, time.Second, "Expected start time did not match")
|
||||
require.WithinDuration(t, tc.expectedEnd, end, time.Second, "Expected end time did not match")
|
||||
require.Equal(t, tc.expectWarning, warnings.EndTimeAdjustedToPastMonth)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ package vault
|
|||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/helper/timeutil"
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
)
|
||||
|
||||
// sendCurrentFragment is a no-op on OSS
|
||||
|
|
@ -22,3 +25,22 @@ func (c *Core) setupClientIDsUsageInfo(ctx context.Context) {
|
|||
// handleClientIDsInMemoryEndOfMonth is a no-op on OSS
|
||||
func (a *ActivityLog) handleClientIDsInMemoryEndOfMonth(ctx context.Context, currentTime time.Time) {
|
||||
}
|
||||
|
||||
// getStartEndTime parses input for start and end times
|
||||
// If the end time is after the end of last month, it is adjusted to the last month
|
||||
func getStartEndTime(d *framework.FieldData, billingStartTime time.Time) (time.Time, time.Time, StartEndTimesWarnings, error) {
|
||||
warnings := StartEndTimesWarnings{}
|
||||
startTime, endTime, err := parseStartEndTimes(d, billingStartTime)
|
||||
if err != nil {
|
||||
return startTime, endTime, warnings, err
|
||||
}
|
||||
// ensure end time is adjusted to the past month if it falls within the current month
|
||||
// or is in a future month
|
||||
now := time.Now().UTC()
|
||||
if !endTime.Before(timeutil.StartOfMonth(now)) {
|
||||
endTime = timeutil.EndOfMonth(timeutil.MonthsPreviousTo(1, timeutil.StartOfMonth(now)))
|
||||
warnings.EndTimeAdjustedToPastMonth = true
|
||||
}
|
||||
|
||||
return startTime, endTime, warnings, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
//go:build testonly && !enterprise
|
||||
|
||||
package activity_testonly
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/helper/testhelpers/minimal"
|
||||
"github.com/hashicorp/vault/helper/timeutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/clientcountutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/clientcountutil/generation"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestACMERegeneration_RegenerateWithCurrentMonth writes segments for previous
|
||||
// months and the current month. The test regenerates the precomputed queries,
|
||||
// and verifies that the counts are correct when querying both with and without
|
||||
// the current month
|
||||
func TestACMERegeneration_RegenerateWithCurrentMonth(t *testing.T) {
|
||||
t.Parallel()
|
||||
cluster := minimal.NewTestSoloCluster(t, &vault.CoreConfig{EnableRaw: true})
|
||||
client := cluster.Cores[0].Client
|
||||
_, err := client.Logical().Write("sys/internal/counters/config", map[string]interface{}{
|
||||
"enabled": "enable",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
now := time.Now().UTC()
|
||||
_, err = clientcountutil.NewActivityLogData(client).
|
||||
NewPreviousMonthData(3).
|
||||
// 3 months ago, 15 non-entity clients and 10 ACME clients
|
||||
NewClientsSeen(15, clientcountutil.WithClientType("non-entity-token")).
|
||||
NewClientsSeen(10, clientcountutil.WithClientType(vault.ACMEActivityType)).
|
||||
NewPreviousMonthData(2).
|
||||
// 2 months ago, 7 new non-entity clients and 5 new ACME clients
|
||||
RepeatedClientsSeen(2, clientcountutil.WithClientType("non-entity-token")).
|
||||
NewClientsSeen(7, clientcountutil.WithClientType("non-entity-token")).
|
||||
RepeatedClientsSeen(5, clientcountutil.WithClientType(vault.ACMEActivityType)).
|
||||
NewClientsSeen(5, clientcountutil.WithClientType(vault.ACMEActivityType)).
|
||||
NewPreviousMonthData(1).
|
||||
// 1 months ago, 4 new non-entity clients and 2 new ACME clients
|
||||
RepeatedClientsSeen(3, clientcountutil.WithClientType("non-entity-token")).
|
||||
NewClientsSeen(4, clientcountutil.WithClientType("non-entity-token")).
|
||||
RepeatedClientsSeen(1, clientcountutil.WithClientType(vault.ACMEActivityType)).
|
||||
NewClientsSeen(2, clientcountutil.WithClientType(vault.ACMEActivityType)).
|
||||
|
||||
// current month, 10 new non-entity clients and 20 new ACME clients
|
||||
NewCurrentMonthData().
|
||||
NewClientsSeen(10, clientcountutil.WithClientType("non-entity-token")).
|
||||
NewClientsSeen(20, clientcountutil.WithClientType(vault.ACMEActivityType)).
|
||||
Write(context.Background(), generation.WriteOptions_WRITE_ENTITIES)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
forceRegeneration(t, cluster)
|
||||
|
||||
startFiveMonthsAgo := timeutil.StartOfMonth(timeutil.MonthsPreviousTo(5, now))
|
||||
endPastMonth := timeutil.EndOfMonth(timeutil.MonthsPreviousTo(1, now))
|
||||
// current month isn't included in this query
|
||||
resp, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"start_time": {startFiveMonthsAgo.Format(time.RFC3339)},
|
||||
"end_time": {endPastMonth.Format(time.RFC3339)},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, vault.ResponseCounts{
|
||||
NonEntityClients: 26,
|
||||
Clients: 43,
|
||||
ACMEClients: 17,
|
||||
}, getTotals(t, resp))
|
||||
// verify start and end times in the response
|
||||
require.Equal(t, resp.Data["start_time"], startFiveMonthsAgo.UTC().Format(time.RFC3339))
|
||||
require.Equal(t, resp.Data["end_time"], endPastMonth.UTC().Format(time.RFC3339))
|
||||
|
||||
// explicitly include the current month in the request
|
||||
// the given end time is adjusted to the last month, excluding the current month at the API
|
||||
respWithCurrent, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"start_time": {startFiveMonthsAgo.Format(time.RFC3339)},
|
||||
"end_time": {timeutil.EndOfMonth(now).Format(time.RFC3339)},
|
||||
})
|
||||
// verify start and end times in the response
|
||||
// end time is expected to be adjusted to the past month, excluding the current month
|
||||
require.Equal(t, resp.Data["start_time"], startFiveMonthsAgo.UTC().Format(time.RFC3339))
|
||||
require.Equal(t, resp.Data["end_time"], endPastMonth.UTC().Format(time.RFC3339))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, vault.ResponseCounts{
|
||||
NonEntityClients: 26,
|
||||
Clients: 43,
|
||||
ACMEClients: 17,
|
||||
}, getTotals(t, respWithCurrent))
|
||||
}
|
||||
|
|
@ -41,72 +41,6 @@ func forceRegeneration(t *testing.T, cluster *vault.TestCluster) {
|
|||
})
|
||||
}
|
||||
|
||||
// TestACMERegeneration_RegenerateWithCurrentMonth writes segments for previous
|
||||
// months and the current month. The test regenerates the precomputed queries,
|
||||
// and verifies that the counts are correct when querying both with and without
|
||||
// the current month
|
||||
func TestACMERegeneration_RegenerateWithCurrentMonth(t *testing.T) {
|
||||
t.Parallel()
|
||||
cluster := minimal.NewTestSoloCluster(t, &vault.CoreConfig{EnableRaw: true})
|
||||
client := cluster.Cores[0].Client
|
||||
_, err := client.Logical().Write("sys/internal/counters/config", map[string]interface{}{
|
||||
"enabled": "enable",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
now := time.Now().UTC()
|
||||
_, err = clientcountutil.NewActivityLogData(client).
|
||||
NewPreviousMonthData(3).
|
||||
// 3 months ago, 15 non-entity clients and 10 ACME clients
|
||||
NewClientsSeen(15, clientcountutil.WithClientType("non-entity-token")).
|
||||
NewClientsSeen(10, clientcountutil.WithClientType(vault.ACMEActivityType)).
|
||||
NewPreviousMonthData(2).
|
||||
// 2 months ago, 7 new non-entity clients and 5 new ACME clients
|
||||
RepeatedClientsSeen(2, clientcountutil.WithClientType("non-entity-token")).
|
||||
NewClientsSeen(7, clientcountutil.WithClientType("non-entity-token")).
|
||||
RepeatedClientsSeen(5, clientcountutil.WithClientType(vault.ACMEActivityType)).
|
||||
NewClientsSeen(5, clientcountutil.WithClientType(vault.ACMEActivityType)).
|
||||
NewPreviousMonthData(1).
|
||||
// 1 months ago, 4 new non-entity clients and 2 new ACME clients
|
||||
RepeatedClientsSeen(3, clientcountutil.WithClientType("non-entity-token")).
|
||||
NewClientsSeen(4, clientcountutil.WithClientType("non-entity-token")).
|
||||
RepeatedClientsSeen(1, clientcountutil.WithClientType(vault.ACMEActivityType)).
|
||||
NewClientsSeen(2, clientcountutil.WithClientType(vault.ACMEActivityType)).
|
||||
|
||||
// current month, 10 new non-entity clients and 20 new ACME clients
|
||||
NewCurrentMonthData().
|
||||
NewClientsSeen(10, clientcountutil.WithClientType("non-entity-token")).
|
||||
NewClientsSeen(20, clientcountutil.WithClientType(vault.ACMEActivityType)).
|
||||
Write(context.Background(), generation.WriteOptions_WRITE_ENTITIES)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
forceRegeneration(t, cluster)
|
||||
|
||||
// current month isn't included in this query
|
||||
resp, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"start_time": {timeutil.StartOfMonth(timeutil.MonthsPreviousTo(5, now)).Format(time.RFC3339)},
|
||||
"end_time": {timeutil.EndOfMonth(timeutil.MonthsPreviousTo(1, now)).Format(time.RFC3339)},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, vault.ResponseCounts{
|
||||
NonEntityClients: 26,
|
||||
Clients: 43,
|
||||
ACMEClients: 17,
|
||||
}, getTotals(t, resp))
|
||||
|
||||
// explicitly include the current month
|
||||
respWithCurrent, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"start_time": {timeutil.StartOfMonth(timeutil.MonthsPreviousTo(5, now)).Format(time.RFC3339)},
|
||||
"end_time": {timeutil.EndOfMonth(now).Format(time.RFC3339)},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, vault.ResponseCounts{
|
||||
NonEntityClients: 36,
|
||||
Clients: 73,
|
||||
ACMEClients: 37,
|
||||
}, getTotals(t, respWithCurrent))
|
||||
}
|
||||
|
||||
// TestACMERegeneration_RegenerateMuchOlder creates segments 5 months ago, 4
|
||||
// months ago, and 3 months ago. The test regenerates the precomputed queries
|
||||
// and then verifies that this older data is included in the generated results.
|
||||
|
|
|
|||
|
|
@ -7,13 +7,21 @@ package activity_testonly
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
"github.com/hashicorp/vault/helper/testhelpers"
|
||||
"github.com/hashicorp/vault/helper/testhelpers/minimal"
|
||||
"github.com/hashicorp/vault/helper/timeutil"
|
||||
vaulthttp "github.com/hashicorp/vault/http"
|
||||
"github.com/hashicorp/vault/sdk/helper/clientcountutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/clientcountutil/generation"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
@ -59,3 +67,516 @@ func Test_ActivityLog_Disable(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Equal(t, ts.UTC(), timeutil.StartOfPreviousMonth(now.UTC()))
|
||||
}
|
||||
|
||||
// Test_ActivityLog_LoseLeadership writes data for the second last month, then causes the
|
||||
// active node to lose leadership. Once a new node becomes the leader, then the
|
||||
// test queries for the second last month data and verifies that the data from
|
||||
// before the leadership transfer is returned
|
||||
func Test_ActivityLog_LoseLeadership(t *testing.T) {
|
||||
t.Parallel()
|
||||
cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
|
||||
HandlerFunc: vaulthttp.Handler,
|
||||
NumCores: 2,
|
||||
})
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
|
||||
active := testhelpers.DeriveStableActiveCore(t, cluster)
|
||||
client := active.Client
|
||||
_, err := client.Logical().Write("sys/internal/counters/config", map[string]interface{}{
|
||||
"enabled": "enable",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = clientcountutil.NewActivityLogData(client).
|
||||
NewPreviousMonthData(1).
|
||||
NewClientsSeen(10).
|
||||
Write(context.Background(), generation.WriteOptions_WRITE_ENTITIES, generation.WriteOptions_WRITE_PRECOMPUTED_QUERIES)
|
||||
require.NoError(t, err)
|
||||
now := time.Now().UTC()
|
||||
|
||||
testhelpers.EnsureCoreSealed(t, active)
|
||||
newActive := testhelpers.WaitForActiveNode(t, cluster)
|
||||
standby := active
|
||||
testhelpers.WaitForStandbyNode(t, standby)
|
||||
testhelpers.EnsureCoreUnsealed(t, cluster, standby)
|
||||
|
||||
endPastMonth := timeutil.EndOfMonth(timeutil.MonthsPreviousTo(1, now))
|
||||
startPastMonth := timeutil.StartOfMonth(timeutil.MonthsPreviousTo(1, now))
|
||||
resp, err := newActive.Client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"end_time": {endPastMonth.Format(time.RFC3339)},
|
||||
"start_time": {startPastMonth.Format(time.RFC3339)},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// verify start and end times in the response
|
||||
require.Equal(t, resp.Data["start_time"], startPastMonth.UTC().Format(time.RFC3339))
|
||||
require.Equal(t, resp.Data["end_time"], endPastMonth.UTC().Format(time.RFC3339))
|
||||
monthResponse := getMonthsData(t, resp)
|
||||
require.Len(t, monthResponse, 1)
|
||||
require.Equal(t, 10, monthResponse[0].NewClients.Counts.Clients)
|
||||
}
|
||||
|
||||
// Test_ActivityLog_ClientsOverlapping writes data for the second last month and
|
||||
// the previous month. In the second last month, 7 new clients are seen. In the previous
|
||||
// month, there are 5 repeated and 2 new clients. The test queries over the
|
||||
// second last and previous months, and verifies that the repeated clients are not
|
||||
// considered new
|
||||
func Test_ActivityLog_ClientsOverlapping(t *testing.T) {
|
||||
t.Parallel()
|
||||
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)
|
||||
_, err = clientcountutil.NewActivityLogData(client).
|
||||
NewPreviousMonthData(2).
|
||||
NewClientsSeen(7).
|
||||
NewPreviousMonthData(1).
|
||||
RepeatedClientsSeen(5).
|
||||
NewClientsSeen(2).
|
||||
Write(context.Background(), generation.WriteOptions_WRITE_PRECOMPUTED_QUERIES, generation.WriteOptions_WRITE_ENTITIES)
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now().UTC()
|
||||
endPastMonth := timeutil.EndOfMonth(timeutil.MonthsPreviousTo(1, now))
|
||||
startTwoMonthsAgo := timeutil.StartOfMonth(timeutil.MonthsPreviousTo(2, now))
|
||||
// query from the beginning of the second last month to the end of the previous month
|
||||
resp, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"end_time": {endPastMonth.Format(time.RFC3339)},
|
||||
"start_time": {startTwoMonthsAgo.Format(time.RFC3339)},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// verify start and end times in the response
|
||||
require.Equal(t, resp.Data["start_time"], startTwoMonthsAgo.UTC().Format(time.RFC3339))
|
||||
require.Equal(t, resp.Data["end_time"], endPastMonth.UTC().Format(time.RFC3339))
|
||||
monthsResponse := getMonthsData(t, resp)
|
||||
require.Len(t, monthsResponse, 2)
|
||||
for _, month := range monthsResponse {
|
||||
ts, err := time.Parse(time.RFC3339, month.Timestamp)
|
||||
require.NoError(t, err)
|
||||
// The previous month should have a total of 7 clients
|
||||
// 2 of those will be considered new
|
||||
if ts.UTC().Equal(timeutil.StartOfMonth(timeutil.MonthsPreviousTo(1, now))) {
|
||||
require.Equal(t, month.Counts.Clients, 7)
|
||||
require.Equal(t, month.NewClients.Counts.Clients, 2)
|
||||
} else {
|
||||
// All clients will be considered new for the second last month
|
||||
require.Equal(t, month.Counts.Clients, 7)
|
||||
require.Equal(t, month.NewClients.Counts.Clients, 7)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test_ActivityLog_ClientsNewCurrentMonth writes data for the second last month and
|
||||
// past month with 5 repeated clients and 2 new clients in the past month.
|
||||
// The test then queries the activity log for only the past month, and
|
||||
// verifies that all 7 clients seen the past month are considered new.
|
||||
func Test_ActivityLog_ClientsNewCurrentMonth(t *testing.T) {
|
||||
t.Parallel()
|
||||
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)
|
||||
_, err = clientcountutil.NewActivityLogData(client).
|
||||
NewPreviousMonthData(2).
|
||||
NewClientsSeen(5).
|
||||
NewPreviousMonthData(1).
|
||||
RepeatedClientsSeen(5).
|
||||
NewClientsSeen(2).
|
||||
Write(context.Background(), generation.WriteOptions_WRITE_PRECOMPUTED_QUERIES, generation.WriteOptions_WRITE_ENTITIES)
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now().UTC()
|
||||
endPastMonth := timeutil.EndOfMonth(timeutil.MonthsPreviousTo(1, now))
|
||||
startPastMonth := timeutil.StartOfMonth(timeutil.MonthsPreviousTo(1, now))
|
||||
// query from the beginning of the second last month to the end of the previous month
|
||||
resp, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"end_time": {endPastMonth.Format(time.RFC3339)},
|
||||
"start_time": {startPastMonth.Format(time.RFC3339)},
|
||||
})
|
||||
// verify start and end times in the response
|
||||
require.Equal(t, resp.Data["start_time"], startPastMonth.UTC().Format(time.RFC3339))
|
||||
require.Equal(t, resp.Data["end_time"], endPastMonth.UTC().Format(time.RFC3339))
|
||||
require.NoError(t, err)
|
||||
monthsResponse := getMonthsData(t, resp)
|
||||
require.Len(t, monthsResponse, 1)
|
||||
require.Equal(t, 7, monthsResponse[0].NewClients.Counts.Clients)
|
||||
}
|
||||
|
||||
// Test_ActivityLog_EmptyDataMonths writes data for only the past month,
|
||||
// then queries a timeframe of several months in the past to now. The test
|
||||
// verifies that empty months of data are returned for the past, and the past
|
||||
// month data is correct.
|
||||
func Test_ActivityLog_EmptyDataMonths(t *testing.T) {
|
||||
t.Parallel()
|
||||
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)
|
||||
_, err = clientcountutil.NewActivityLogData(client).
|
||||
NewPreviousMonthData(1).
|
||||
NewClientsSeen(10).
|
||||
Write(context.Background(), generation.WriteOptions_WRITE_PRECOMPUTED_QUERIES, generation.WriteOptions_WRITE_ENTITIES)
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now().UTC()
|
||||
endPastMonth := timeutil.EndOfMonth(timeutil.MonthsPreviousTo(1, now))
|
||||
startFourMonthsAgo := timeutil.StartOfMonth(timeutil.MonthsPreviousTo(4, now))
|
||||
// query from the beginning of 4 months ago to the end of past month
|
||||
resp, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"end_time": {endPastMonth.Format(time.RFC3339)},
|
||||
"start_time": {startFourMonthsAgo.Format(time.RFC3339)},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// verify start and end times in the response
|
||||
require.Equal(t, resp.Data["start_time"], startFourMonthsAgo.UTC().Format(time.RFC3339))
|
||||
require.Equal(t, resp.Data["end_time"], endPastMonth.UTC().Format(time.RFC3339))
|
||||
monthsResponse := getMonthsData(t, resp)
|
||||
|
||||
require.Len(t, monthsResponse, 4)
|
||||
for _, month := range monthsResponse {
|
||||
ts, err := time.Parse(time.RFC3339, month.Timestamp)
|
||||
require.NoError(t, err)
|
||||
// past month should have data
|
||||
if ts.UTC().Equal(timeutil.StartOfMonth(timeutil.MonthsPreviousTo(1, now))) {
|
||||
require.Equal(t, month.Counts.Clients, 10)
|
||||
} else {
|
||||
// other months should be empty
|
||||
require.Nil(t, month.Counts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test_ActivityLog_FutureEndDate queries a start time from the past
|
||||
// and an end date in the future. The test
|
||||
// verifies that the current month is returned in the response.
|
||||
func Test_ActivityLog_FutureEndDate(t *testing.T) {
|
||||
t.Parallel()
|
||||
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)
|
||||
_, err = clientcountutil.NewActivityLogData(client).
|
||||
NewPreviousMonthData(1).
|
||||
NewClientsSeen(10).
|
||||
NewCurrentMonthData().
|
||||
NewClientsSeen(10).
|
||||
Write(context.Background(), generation.WriteOptions_WRITE_PRECOMPUTED_QUERIES, generation.WriteOptions_WRITE_ENTITIES)
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now().UTC()
|
||||
startThreeMonthsAgo := timeutil.StartOfMonth(timeutil.MonthsPreviousTo(3, now))
|
||||
// query from the beginning of 3 months ago to beginning of next month
|
||||
resp, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"end_time": {timeutil.StartOfNextMonth(now).Format(time.RFC3339)},
|
||||
"start_time": {startThreeMonthsAgo.Format(time.RFC3339)},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
endPastMonth := timeutil.EndOfMonth(timeutil.MonthsPreviousTo(1, now))
|
||||
// verify start and end times in the response
|
||||
// end time must be adjusted to past month if within current month or in future date
|
||||
require.Equal(t, resp.Data["start_time"], startThreeMonthsAgo.UTC().Format(time.RFC3339))
|
||||
require.Equal(t, resp.Data["end_time"], endPastMonth.UTC().Format(time.RFC3339))
|
||||
monthsResponse := getMonthsData(t, resp)
|
||||
|
||||
require.Len(t, monthsResponse, 3)
|
||||
|
||||
// Get the last month of data in the slice
|
||||
expectedCurrentMonthData := monthsResponse[2]
|
||||
expectedTime, err := time.Parse(time.RFC3339, expectedCurrentMonthData.Timestamp)
|
||||
require.NoError(t, err)
|
||||
if !timeutil.IsCurrentMonth(expectedTime, endPastMonth) {
|
||||
t.Fatalf("final month data is not past month")
|
||||
}
|
||||
}
|
||||
|
||||
// Test_ActivityLog_ClientTypeResponse runs for each client type. In the
|
||||
// subtests, 10 clients of the type are created and the test verifies that the
|
||||
// activity log query response returns 10 clients of that type at every level of
|
||||
// the response hierarchy
|
||||
func Test_ActivityLog_ClientTypeResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, tc := range allClientTypeTestCases {
|
||||
tc := tc
|
||||
t.Run(tc.clientType, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
cluster := minimal.NewTestSoloCluster(t, nil)
|
||||
client := cluster.Cores[0].Client
|
||||
_, err := client.Logical().Write("sys/internal/counters/config", map[string]interface{}{
|
||||
"enabled": "enable",
|
||||
})
|
||||
_, err = clientcountutil.NewActivityLogData(client).
|
||||
NewPreviousMonthData(1).
|
||||
NewClientsSeen(10, clientcountutil.WithClientType(tc.clientType)).
|
||||
Write(context.Background(), generation.WriteOptions_WRITE_ENTITIES, generation.WriteOptions_WRITE_PRECOMPUTED_QUERIES)
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now().UTC()
|
||||
endPastMonth := timeutil.EndOfMonth(timeutil.MonthsPreviousTo(1, now))
|
||||
startPastMonth := timeutil.StartOfMonth(timeutil.MonthsPreviousTo(1, now))
|
||||
resp, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"end_time": {endPastMonth.Format(time.RFC3339)},
|
||||
"start_time": {startPastMonth.Format(time.RFC3339)},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// verify start and end times in the response
|
||||
require.Equal(t, resp.Data["start_time"], startPastMonth.UTC().Format(time.RFC3339))
|
||||
require.Equal(t, resp.Data["end_time"], endPastMonth.UTC().Format(time.RFC3339))
|
||||
|
||||
total := getTotals(t, resp)
|
||||
require.Equal(t, 10, tc.responseCountsFn(total))
|
||||
require.Equal(t, 10, total.Clients)
|
||||
|
||||
byNamespace := getNamespaceData(t, resp)
|
||||
require.Equal(t, 10, tc.responseCountsFn(byNamespace[0].Counts))
|
||||
require.Equal(t, 10, tc.responseCountsFn(*byNamespace[0].Mounts[0].Counts))
|
||||
require.Equal(t, 10, byNamespace[0].Counts.Clients)
|
||||
require.Equal(t, 10, byNamespace[0].Mounts[0].Counts.Clients)
|
||||
|
||||
byMonth := getMonthsData(t, resp)
|
||||
require.Equal(t, 10, tc.responseCountsFn(*byMonth[0].NewClients.Counts))
|
||||
require.Equal(t, 10, tc.responseCountsFn(*byMonth[0].Counts))
|
||||
require.Equal(t, 10, tc.responseCountsFn(byMonth[0].Namespaces[0].Counts))
|
||||
require.Equal(t, 10, tc.responseCountsFn(*byMonth[0].Namespaces[0].Mounts[0].Counts))
|
||||
require.Equal(t, 10, byMonth[0].NewClients.Counts.Clients)
|
||||
require.Equal(t, 10, byMonth[0].Counts.Clients)
|
||||
require.Equal(t, 10, byMonth[0].Namespaces[0].Counts.Clients)
|
||||
require.Equal(t, 10, byMonth[0].Namespaces[0].Mounts[0].Counts.Clients)
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Test_ActivityLog_MountDeduplication writes data for the second last
|
||||
// month across 4 mounts. The cubbyhole and sys mounts have clients in the
|
||||
// past month as well. The test verifies that the mount counts are correctly
|
||||
// summed in the results when the second last month and past month are queried.
|
||||
func Test_ActivityLog_MountDeduplication(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
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)
|
||||
now := time.Now().UTC()
|
||||
|
||||
_, err = clientcountutil.NewActivityLogData(client).
|
||||
NewPreviousMonthData(2).
|
||||
NewClientSeen(clientcountutil.WithClientMount("sys")).
|
||||
NewClientSeen(clientcountutil.WithClientMount("secret")).
|
||||
NewClientSeen(clientcountutil.WithClientMount("cubbyhole")).
|
||||
NewClientSeen(clientcountutil.WithClientMount("identity")).
|
||||
NewPreviousMonthData(1).
|
||||
NewClientSeen(clientcountutil.WithClientMount("cubbyhole")).
|
||||
NewClientSeen(clientcountutil.WithClientMount("sys")).
|
||||
Write(context.Background(), generation.WriteOptions_WRITE_PRECOMPUTED_QUERIES, generation.WriteOptions_WRITE_ENTITIES)
|
||||
require.NoError(t, err)
|
||||
|
||||
endPastMonth := timeutil.EndOfMonth(timeutil.MonthsPreviousTo(1, now))
|
||||
startTwoMonthsAgo := timeutil.StartOfMonth(timeutil.MonthsPreviousTo(2, now))
|
||||
resp, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"end_time": {endPastMonth.Format(time.RFC3339)},
|
||||
"start_time": {startTwoMonthsAgo.Format(time.RFC3339)},
|
||||
})
|
||||
// verify start and end times in the response
|
||||
require.Equal(t, resp.Data["start_time"], startTwoMonthsAgo.UTC().Format(time.RFC3339))
|
||||
require.Equal(t, resp.Data["end_time"], endPastMonth.UTC().Format(time.RFC3339))
|
||||
|
||||
require.NoError(t, err)
|
||||
byNamespace := getNamespaceData(t, resp)
|
||||
require.Len(t, byNamespace, 1)
|
||||
require.Len(t, byNamespace[0].Mounts, 4)
|
||||
mountSet := make(map[string]int, 4)
|
||||
for _, mount := range byNamespace[0].Mounts {
|
||||
mountSet[mount.MountPath] = mount.Counts.Clients
|
||||
}
|
||||
require.Equal(t, map[string]int{
|
||||
"identity/": 1,
|
||||
"sys/": 2,
|
||||
"cubbyhole/": 2,
|
||||
"secret/": 1,
|
||||
}, 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 three months ago data
|
||||
activityLogGenerator = activityLogGenerator.NewPreviousMonthData(3)
|
||||
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 two months ago data
|
||||
activityLogGenerator = activityLogGenerator.NewPreviousMonthData(2)
|
||||
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 previous month data
|
||||
activityLogGenerator = activityLogGenerator.NewPreviousMonthData(1)
|
||||
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)
|
||||
|
||||
endPastMonth := timeutil.EndOfMonth(timeutil.MonthsPreviousTo(1, time.Now()).UTC())
|
||||
startThreeMonthsAgo := timeutil.StartOfMonth(timeutil.MonthsPreviousTo(3, time.Now().UTC()))
|
||||
|
||||
// query activity log
|
||||
resp, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"end_time": {endPastMonth.Format(time.RFC3339)},
|
||||
"start_time": {startThreeMonthsAgo.Format(time.RFC3339)},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// verify start and end times in the response
|
||||
require.Equal(t, resp.Data["start_time"], startThreeMonthsAgo.UTC().Format(time.RFC3339))
|
||||
require.Equal(t, resp.Data["end_time"], endPastMonth.UTC().Format(time.RFC3339))
|
||||
|
||||
// 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,20 +10,15 @@ import (
|
|||
"context"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
"github.com/hashicorp/vault/helper/testhelpers"
|
||||
"github.com/hashicorp/vault/helper/testhelpers/minimal"
|
||||
"github.com/hashicorp/vault/helper/timeutil"
|
||||
vaulthttp "github.com/hashicorp/vault/http"
|
||||
"github.com/hashicorp/vault/sdk/helper/clientcountutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/clientcountutil/generation"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
|
|
@ -66,211 +61,6 @@ var allClientTypeTestCases = []struct {
|
|||
},
|
||||
}
|
||||
|
||||
// Test_ActivityLog_LoseLeadership writes data for this month, then causes the
|
||||
// active node to lose leadership. Once a new node becomes the leader, then the
|
||||
// test queries for the current month data and verifies that the data from
|
||||
// before the leadership transfer is returned
|
||||
func Test_ActivityLog_LoseLeadership(t *testing.T) {
|
||||
t.Parallel()
|
||||
cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
|
||||
HandlerFunc: vaulthttp.Handler,
|
||||
NumCores: 2,
|
||||
})
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
|
||||
active := testhelpers.DeriveStableActiveCore(t, cluster)
|
||||
client := active.Client
|
||||
_, err := client.Logical().Write("sys/internal/counters/config", map[string]interface{}{
|
||||
"enabled": "enable",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = clientcountutil.NewActivityLogData(client).
|
||||
NewCurrentMonthData().
|
||||
NewClientsSeen(10).
|
||||
Write(context.Background(), generation.WriteOptions_WRITE_ENTITIES)
|
||||
require.NoError(t, err)
|
||||
now := time.Now().UTC()
|
||||
|
||||
testhelpers.EnsureCoreSealed(t, active)
|
||||
newActive := testhelpers.WaitForActiveNode(t, cluster)
|
||||
standby := active
|
||||
testhelpers.WaitForStandbyNode(t, standby)
|
||||
testhelpers.EnsureCoreUnsealed(t, cluster, standby)
|
||||
|
||||
resp, err := newActive.Client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"end_time": {timeutil.EndOfMonth(now).Format(time.RFC3339)},
|
||||
"start_time": {timeutil.StartOfMonth(now).Format(time.RFC3339)},
|
||||
})
|
||||
monthResponse := getMonthsData(t, resp)
|
||||
require.Len(t, monthResponse, 1)
|
||||
require.Equal(t, 10, monthResponse[0].NewClients.Counts.Clients)
|
||||
}
|
||||
|
||||
// Test_ActivityLog_ClientsOverlapping writes data for the previous month and
|
||||
// current month. In the previous month, 7 new clients are seen. In the current
|
||||
// month, there are 5 repeated and 2 new clients. The test queries over the
|
||||
// previous and current months, and verifies that the repeated clients are not
|
||||
// considered new
|
||||
func Test_ActivityLog_ClientsOverlapping(t *testing.T) {
|
||||
t.Parallel()
|
||||
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)
|
||||
_, err = clientcountutil.NewActivityLogData(client).
|
||||
NewPreviousMonthData(1).
|
||||
NewClientsSeen(7).
|
||||
NewCurrentMonthData().
|
||||
RepeatedClientsSeen(5).
|
||||
NewClientsSeen(2).
|
||||
Write(context.Background(), generation.WriteOptions_WRITE_PRECOMPUTED_QUERIES, generation.WriteOptions_WRITE_ENTITIES)
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
// query from the beginning of the previous month to the end of this month
|
||||
resp, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"end_time": {timeutil.EndOfMonth(now).Format(time.RFC3339)},
|
||||
"start_time": {timeutil.StartOfMonth(timeutil.MonthsPreviousTo(1, now)).Format(time.RFC3339)},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
monthsResponse := getMonthsData(t, resp)
|
||||
require.Len(t, monthsResponse, 2)
|
||||
for _, month := range monthsResponse {
|
||||
ts, err := time.Parse(time.RFC3339, month.Timestamp)
|
||||
require.NoError(t, err)
|
||||
// This month should have a total of 7 clients
|
||||
// 2 of those will be considered new
|
||||
if ts.UTC().Equal(timeutil.StartOfMonth(now)) {
|
||||
require.Equal(t, month.Counts.Clients, 7)
|
||||
require.Equal(t, month.NewClients.Counts.Clients, 2)
|
||||
} else {
|
||||
// All clients will be considered new for the previous month
|
||||
require.Equal(t, month.Counts.Clients, 7)
|
||||
require.Equal(t, month.NewClients.Counts.Clients, 7)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test_ActivityLog_ClientsNewCurrentMonth writes data for the past month and
|
||||
// current month with 5 repeated clients and 2 new clients in the current month.
|
||||
// The test then queries the activity log for only the current month, and
|
||||
// verifies that all 7 clients seen this month are considered new.
|
||||
func Test_ActivityLog_ClientsNewCurrentMonth(t *testing.T) {
|
||||
t.Parallel()
|
||||
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)
|
||||
_, err = clientcountutil.NewActivityLogData(client).
|
||||
NewPreviousMonthData(1).
|
||||
NewClientsSeen(5).
|
||||
NewCurrentMonthData().
|
||||
RepeatedClientsSeen(5).
|
||||
NewClientsSeen(2).
|
||||
Write(context.Background(), generation.WriteOptions_WRITE_PRECOMPUTED_QUERIES, generation.WriteOptions_WRITE_ENTITIES)
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
// query from the beginning of this month to the end of this month
|
||||
resp, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"end_time": {timeutil.EndOfMonth(now).Format(time.RFC3339)},
|
||||
"start_time": {timeutil.StartOfMonth(now).Format(time.RFC3339)},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
monthsResponse := getMonthsData(t, resp)
|
||||
require.Len(t, monthsResponse, 1)
|
||||
require.Equal(t, 7, monthsResponse[0].NewClients.Counts.Clients)
|
||||
}
|
||||
|
||||
// Test_ActivityLog_EmptyDataMonths writes data for only the current month,
|
||||
// then queries a timeframe of several months in the past to now. The test
|
||||
// verifies that empty months of data are returned for the past, and the current
|
||||
// month data is correct.
|
||||
func Test_ActivityLog_EmptyDataMonths(t *testing.T) {
|
||||
t.Parallel()
|
||||
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)
|
||||
_, err = clientcountutil.NewActivityLogData(client).
|
||||
NewCurrentMonthData().
|
||||
NewClientsSeen(10).
|
||||
Write(context.Background(), generation.WriteOptions_WRITE_PRECOMPUTED_QUERIES, generation.WriteOptions_WRITE_ENTITIES)
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now().UTC()
|
||||
// query from the beginning of 3 months ago to the end of this month
|
||||
resp, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"end_time": {timeutil.EndOfMonth(now).Format(time.RFC3339)},
|
||||
"start_time": {timeutil.StartOfMonth(timeutil.MonthsPreviousTo(3, now)).Format(time.RFC3339)},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
monthsResponse := getMonthsData(t, resp)
|
||||
|
||||
require.Len(t, monthsResponse, 4)
|
||||
for _, month := range monthsResponse {
|
||||
ts, err := time.Parse(time.RFC3339, month.Timestamp)
|
||||
require.NoError(t, err)
|
||||
// current month should have data
|
||||
if ts.UTC().Equal(timeutil.StartOfMonth(now)) {
|
||||
require.Equal(t, month.Counts.Clients, 10)
|
||||
} else {
|
||||
// other months should be empty
|
||||
require.Nil(t, month.Counts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test_ActivityLog_FutureEndDate queries a start time from the past
|
||||
// and an end date in the future. The test
|
||||
// verifies that the current month is returned in the response.
|
||||
func Test_ActivityLog_FutureEndDate(t *testing.T) {
|
||||
t.Parallel()
|
||||
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)
|
||||
_, err = clientcountutil.NewActivityLogData(client).
|
||||
NewPreviousMonthData(1).
|
||||
NewClientsSeen(10).
|
||||
NewCurrentMonthData().
|
||||
NewClientsSeen(10).
|
||||
Write(context.Background(), generation.WriteOptions_WRITE_PRECOMPUTED_QUERIES, generation.WriteOptions_WRITE_ENTITIES)
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now().UTC()
|
||||
// query from the beginning of 3 months ago to beginning of next month
|
||||
resp, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"end_time": {timeutil.StartOfNextMonth(now).Format(time.RFC3339)},
|
||||
"start_time": {timeutil.StartOfMonth(timeutil.MonthsPreviousTo(3, now)).Format(time.RFC3339)},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
monthsResponse := getMonthsData(t, resp)
|
||||
|
||||
require.Len(t, monthsResponse, 4)
|
||||
|
||||
// Get the last month of data in the slice
|
||||
expectedCurrentMonthData := monthsResponse[3]
|
||||
expectedTime, err := time.Parse(time.RFC3339, expectedCurrentMonthData.Timestamp)
|
||||
require.NoError(t, err)
|
||||
if !timeutil.IsCurrentMonth(expectedTime, now) {
|
||||
t.Fatalf("final month data is not current month")
|
||||
}
|
||||
}
|
||||
|
||||
func getMonthsData(t *testing.T, resp *api.Secret) []vault.ResponseMonth {
|
||||
t.Helper()
|
||||
monthsRaw, ok := resp.Data["months"]
|
||||
|
|
@ -301,58 +91,6 @@ func getTotals(t *testing.T, resp *api.Secret) vault.ResponseCounts {
|
|||
return total
|
||||
}
|
||||
|
||||
// Test_ActivityLog_ClientTypeResponse runs for each client type. In the
|
||||
// subtests, 10 clients of the type are created and the test verifies that the
|
||||
// activity log query response returns 10 clients of that type at every level of
|
||||
// the response hierarchy
|
||||
func Test_ActivityLog_ClientTypeResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, tc := range allClientTypeTestCases {
|
||||
tc := tc
|
||||
t.Run(tc.clientType, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
cluster := minimal.NewTestSoloCluster(t, nil)
|
||||
client := cluster.Cores[0].Client
|
||||
_, err := client.Logical().Write("sys/internal/counters/config", map[string]interface{}{
|
||||
"enabled": "enable",
|
||||
})
|
||||
_, err = clientcountutil.NewActivityLogData(client).
|
||||
NewCurrentMonthData().
|
||||
NewClientsSeen(10, clientcountutil.WithClientType(tc.clientType)).
|
||||
Write(context.Background(), generation.WriteOptions_WRITE_ENTITIES)
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now().UTC()
|
||||
resp, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"end_time": {timeutil.EndOfMonth(now).Format(time.RFC3339)},
|
||||
"start_time": {timeutil.StartOfMonth(now).Format(time.RFC3339)},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
total := getTotals(t, resp)
|
||||
require.Equal(t, 10, tc.responseCountsFn(total))
|
||||
require.Equal(t, 10, total.Clients)
|
||||
|
||||
byNamespace := getNamespaceData(t, resp)
|
||||
require.Equal(t, 10, tc.responseCountsFn(byNamespace[0].Counts))
|
||||
require.Equal(t, 10, tc.responseCountsFn(*byNamespace[0].Mounts[0].Counts))
|
||||
require.Equal(t, 10, byNamespace[0].Counts.Clients)
|
||||
require.Equal(t, 10, byNamespace[0].Mounts[0].Counts.Clients)
|
||||
|
||||
byMonth := getMonthsData(t, resp)
|
||||
require.Equal(t, 10, tc.responseCountsFn(*byMonth[0].NewClients.Counts))
|
||||
require.Equal(t, 10, tc.responseCountsFn(*byMonth[0].Counts))
|
||||
require.Equal(t, 10, tc.responseCountsFn(byMonth[0].Namespaces[0].Counts))
|
||||
require.Equal(t, 10, tc.responseCountsFn(*byMonth[0].Namespaces[0].Mounts[0].Counts))
|
||||
require.Equal(t, 10, byMonth[0].NewClients.Counts.Clients)
|
||||
require.Equal(t, 10, byMonth[0].Counts.Clients)
|
||||
require.Equal(t, 10, byMonth[0].Namespaces[0].Counts.Clients)
|
||||
require.Equal(t, 10, byMonth[0].Namespaces[0].Mounts[0].Counts.Clients)
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Test_ActivityLogCurrentMonth_Response runs for each client type. The subtest
|
||||
// creates 10 clients of the type and verifies that the activity log partial
|
||||
// month response returns 10 clients of that type at every level of the response
|
||||
|
|
@ -447,54 +185,6 @@ func Test_ActivityLog_Deduplication(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// Test_ActivityLog_MountDeduplication writes data for the previous
|
||||
// month across 4 mounts. The cubbyhole and sys mounts have clients in the
|
||||
// current month as well. The test verifies that the mount counts are correctly
|
||||
// summed in the results when the previous and current month are queried.
|
||||
func Test_ActivityLog_MountDeduplication(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
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)
|
||||
now := time.Now().UTC()
|
||||
|
||||
_, err = clientcountutil.NewActivityLogData(client).
|
||||
NewPreviousMonthData(1).
|
||||
NewClientSeen(clientcountutil.WithClientMount("sys")).
|
||||
NewClientSeen(clientcountutil.WithClientMount("secret")).
|
||||
NewClientSeen(clientcountutil.WithClientMount("cubbyhole")).
|
||||
NewClientSeen(clientcountutil.WithClientMount("identity")).
|
||||
NewCurrentMonthData().
|
||||
NewClientSeen(clientcountutil.WithClientMount("cubbyhole")).
|
||||
NewClientSeen(clientcountutil.WithClientMount("sys")).
|
||||
Write(context.Background(), generation.WriteOptions_WRITE_PRECOMPUTED_QUERIES, generation.WriteOptions_WRITE_ENTITIES)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{
|
||||
"end_time": {timeutil.EndOfMonth(now).Format(time.RFC3339)},
|
||||
"start_time": {timeutil.StartOfMonth(timeutil.MonthsPreviousTo(1, now)).Format(time.RFC3339)},
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
byNamespace := getNamespaceData(t, resp)
|
||||
require.Len(t, byNamespace, 1)
|
||||
require.Len(t, byNamespace[0].Mounts, 4)
|
||||
mountSet := make(map[string]int, 4)
|
||||
for _, mount := range byNamespace[0].Mounts {
|
||||
mountSet[mount.MountPath] = mount.Counts.Clients
|
||||
}
|
||||
require.Equal(t, map[string]int{
|
||||
"identity/": 1,
|
||||
"sys/": 2,
|
||||
"cubbyhole/": 2,
|
||||
"secret/": 1,
|
||||
}, mountSet)
|
||||
}
|
||||
|
||||
// getJSONExport is used to fetch activity export records using json format.
|
||||
// The records will be returned as a map keyed by client ID.
|
||||
func getJSONExport(t *testing.T, client *api.Client, startTime time.Time, now time.Time) (map[string]vault.ActivityLogExportRecord, error) {
|
||||
|
|
@ -720,172 +410,3 @@ path "sys/internal/counters/activity/export" {
|
|||
require.NoError(t, err)
|
||||
require.Len(t, clients, 10)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,8 +29,19 @@ const (
|
|||
|
||||
// WarningCurrentMonthIsAnEstimate is a warning string that is used to let the customer know that for this query, the current month's data is estimated.
|
||||
WarningCurrentMonthIsAnEstimate = "Since this usage period includes both the current month and at least one historical month, counts returned in this usage period are an estimate. Client counts for this period will no longer be estimated at the start of the next month."
|
||||
|
||||
// WarningProvidedStartAndEndTimesIgnored is a warning string that is used to indicate that the provided start and end times by the user have been aligned to a billing period's start and end times
|
||||
WarningProvidedStartAndEndTimesIgnored = "start_time and end_time parameters can only be used to specify the beginning or end of the same billing period. The values provided for these parameters are not supported and are ignored. Showing the data for the entire billing period corresponding to start_time. If start_time is not provided, the billing period is determined based on the end_time."
|
||||
|
||||
// WarningEndTimeAsCurrentMonthOrFutureIgnored is a warning string that is used to indicate the provided end time has been adjusted to the previous month if it was provided to be within the current month or in future date
|
||||
WarningEndTimeAsCurrentMonthOrFutureIgnored = "end_time parameter can only be used to specify a date until the end of previous month. The value provided for this parameter was in the current month or in the future date and was therefore ignored. The response includes data until the end of the previous month."
|
||||
)
|
||||
|
||||
type StartEndTimesWarnings struct {
|
||||
TimesAlignedToBilling bool
|
||||
EndTimeAdjustedToPastMonth bool
|
||||
}
|
||||
|
||||
// activityQueryPath is available in every namespace
|
||||
func (b *SystemBackend) activityQueryPath() *framework.Path {
|
||||
return &framework.Path{
|
||||
|
|
@ -332,7 +343,6 @@ func (b *SystemBackend) handleClientExport(ctx context.Context, req *logical.Req
|
|||
}
|
||||
|
||||
func (b *SystemBackend) handleClientMetricQuery(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
var startTime, endTime time.Time
|
||||
b.Core.activityLogLock.RLock()
|
||||
a := b.Core.activityLog
|
||||
b.Core.activityLogLock.RUnlock()
|
||||
|
|
@ -347,7 +357,8 @@ func (b *SystemBackend) handleClientMetricQuery(ctx context.Context, req *logica
|
|||
}
|
||||
|
||||
var err error
|
||||
startTime, endTime, err = parseStartEndTimes(d, b.Core.BillingStart())
|
||||
var timeWarnings StartEndTimesWarnings
|
||||
startTime, endTime, timeWarnings, err := getStartEndTime(d, b.Core.BillingStart())
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
|
|
@ -371,6 +382,12 @@ func (b *SystemBackend) handleClientMetricQuery(ctx context.Context, req *logica
|
|||
if queryContainsEstimates(startTime, endTime) {
|
||||
warnings = append(warnings, WarningCurrentMonthIsAnEstimate)
|
||||
}
|
||||
if timeWarnings.EndTimeAdjustedToPastMonth {
|
||||
warnings = append(warnings, WarningEndTimeAsCurrentMonthOrFutureIgnored)
|
||||
}
|
||||
if timeWarnings.TimesAlignedToBilling {
|
||||
warnings = append(warnings, WarningProvidedStartAndEndTimesIgnored)
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
Warnings: warnings,
|
||||
|
|
|
|||
Loading…
Reference in a new issue