mirror of https://github.com/hashicorp/boundary
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
428 lines
11 KiB
428 lines
11 KiB
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package event
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"os"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
|
"github.com/hashicorp/boundary/internal/gen/controller/servers"
|
|
"github.com/hashicorp/boundary/internal/gen/controller/servers/services"
|
|
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/groups"
|
|
"github.com/hashicorp/eventlogger"
|
|
"github.com/hashicorp/eventlogger/formatter_filters/cloudevents"
|
|
"github.com/hashicorp/go-hclog"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
|
)
|
|
|
|
// CloudEventFromFile will marshal a single cloud event from the provided file
|
|
// name
|
|
func CloudEventFromFile(t testing.TB, fileName string) *cloudevents.Event {
|
|
t.Helper()
|
|
b, err := os.ReadFile(fileName)
|
|
assert.NoError(t, err)
|
|
got := &cloudevents.Event{}
|
|
err = json.Unmarshal(b, got)
|
|
require.NoErrorf(t, err, "json: %s", string(b))
|
|
return got
|
|
}
|
|
|
|
// CloudEventFromBuf will marshal a single cloud event from the provided buffer
|
|
func CloudEventFromBuf(t testing.TB, b []byte) *cloudevents.Event {
|
|
t.Helper()
|
|
got := &cloudevents.Event{}
|
|
err := json.Unmarshal(b, got)
|
|
require.NoErrorf(t, err, "json: %s", string(b))
|
|
return got
|
|
}
|
|
|
|
// TestWithoutEventing allows the caller to "disable" all eventing for a test.
|
|
// You must not run the test in parallel (no calls to t.Parallel) since the
|
|
// function relies on modifying the system wide default eventer.
|
|
func TestWithoutEventing(t testing.TB) *Eventer {
|
|
t.Helper()
|
|
require := require.New(t)
|
|
testConfig := EventerConfig{
|
|
AuditEnabled: false,
|
|
ObservationsEnabled: false,
|
|
SysEventsEnabled: false,
|
|
}
|
|
testLock := &sync.Mutex{}
|
|
testLogger := hclog.New(&hclog.LoggerOptions{
|
|
Mutex: testLock,
|
|
Output: io.Discard,
|
|
})
|
|
testEventer, err := NewEventer(testLogger, testLock, "TestWithoutEventing", testConfig, withNoDefaultSink(t))
|
|
require.NoError(err)
|
|
|
|
require.NoError(InitSysEventer(testLogger, testLock, "TestWithoutEventing", WithEventer(testEventer)))
|
|
return testEventer
|
|
}
|
|
|
|
// TestGetEventerConfig is a test accessor for the eventer's config
|
|
func TestGetEventerConfig(t testing.TB, e *Eventer) EventerConfig {
|
|
t.Helper()
|
|
return e.conf
|
|
}
|
|
|
|
// TestResetSysEventer will reset event.syseventer to an uninitialized state.
|
|
func TestResetSystEventer(t testing.TB) {
|
|
t.Helper()
|
|
sysEventerLock.Lock()
|
|
defer sysEventerLock.Unlock()
|
|
sysEventer = nil
|
|
}
|
|
|
|
type TestConfig struct {
|
|
EventerConfig EventerConfig
|
|
AllEvents *os.File
|
|
ErrorEvents *os.File
|
|
ObservationEvents *os.File
|
|
AuditEvents *os.File
|
|
}
|
|
|
|
// TestEventerConfig creates a test config and registers a cleanup func for its
|
|
// test tmp files.
|
|
func TestEventerConfig(t testing.TB, testName string, opt ...Option) TestConfig {
|
|
t.Helper()
|
|
require := require.New(t)
|
|
tmpAllFile, err := os.CreateTemp("./", "tmp-all-events-"+testName)
|
|
require.NoError(err)
|
|
|
|
tmpErrFile, err := os.CreateTemp("./", "tmp-errors-"+testName)
|
|
require.NoError(err)
|
|
|
|
t.Cleanup(func() {
|
|
os.Remove(tmpAllFile.Name())
|
|
os.Remove(tmpErrFile.Name())
|
|
})
|
|
|
|
opts := getOpts(opt...)
|
|
if opts.withSinkFormat == "" {
|
|
opts.withSinkFormat = JSONSinkFormat
|
|
}
|
|
|
|
c := TestConfig{
|
|
EventerConfig: EventerConfig{
|
|
ObservationsEnabled: true,
|
|
AuditEnabled: true,
|
|
SysEventsEnabled: true,
|
|
Sinks: []*SinkConfig{
|
|
{
|
|
Name: "every-type-file-sink",
|
|
Type: FileSink,
|
|
EventTypes: []Type{EveryType},
|
|
Format: opts.withSinkFormat,
|
|
FileConfig: &FileSinkTypeConfig{
|
|
Path: "./",
|
|
FileName: tmpAllFile.Name(),
|
|
},
|
|
AuditConfig: DefaultAuditConfig(),
|
|
},
|
|
{
|
|
Name: "err-file-sink",
|
|
Type: FileSink,
|
|
EventTypes: []Type{ErrorType},
|
|
Format: opts.withSinkFormat,
|
|
FileConfig: &FileSinkTypeConfig{
|
|
Path: "./",
|
|
FileName: tmpErrFile.Name(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
AllEvents: tmpAllFile,
|
|
ErrorEvents: tmpErrFile,
|
|
}
|
|
if opts.withStderrSink {
|
|
c.EventerConfig.Sinks = append(c.EventerConfig.Sinks, &SinkConfig{
|
|
Name: "stderr",
|
|
Type: StderrSink,
|
|
EventTypes: []Type{EveryType},
|
|
Format: opts.withSinkFormat,
|
|
AuditConfig: DefaultAuditConfig(),
|
|
})
|
|
}
|
|
if opts.withAuditSink {
|
|
tmpFile, err := os.CreateTemp("./", "tmp-audit-"+testName)
|
|
require.NoError(err)
|
|
t.Cleanup(func() {
|
|
os.Remove(tmpFile.Name())
|
|
})
|
|
c.EventerConfig.Sinks = append(c.EventerConfig.Sinks, &SinkConfig{
|
|
Name: "audit-file-sink",
|
|
Type: FileSink,
|
|
EventTypes: []Type{AuditType},
|
|
Format: opts.withSinkFormat,
|
|
FileConfig: &FileSinkTypeConfig{
|
|
Path: "./",
|
|
FileName: tmpFile.Name(),
|
|
},
|
|
AuditConfig: DefaultAuditConfig(),
|
|
})
|
|
c.AuditEvents = tmpFile
|
|
}
|
|
if opts.withObservationSink {
|
|
tmpFile, err := os.CreateTemp("./", "tmp-observation-"+testName)
|
|
require.NoError(err)
|
|
t.Cleanup(func() {
|
|
os.Remove(tmpFile.Name())
|
|
})
|
|
c.EventerConfig.Sinks = append(c.EventerConfig.Sinks, &SinkConfig{
|
|
Name: "err-observation-sink",
|
|
Type: FileSink,
|
|
EventTypes: []Type{ObservationType},
|
|
Format: opts.withSinkFormat,
|
|
FileConfig: &FileSinkTypeConfig{
|
|
Path: "./",
|
|
FileName: tmpFile.Name(),
|
|
},
|
|
})
|
|
c.ObservationEvents = tmpFile
|
|
}
|
|
if opts.withSysSink {
|
|
tmpFile, err := os.CreateTemp("./", "tmp-sysevents-"+testName)
|
|
require.NoError(err)
|
|
t.Cleanup(func() {
|
|
os.Remove(tmpFile.Name())
|
|
})
|
|
c.EventerConfig.Sinks = append(c.EventerConfig.Sinks, &SinkConfig{
|
|
Name: "err-sysevents-sink",
|
|
Type: FileSink,
|
|
EventTypes: []Type{SystemType},
|
|
Format: opts.withSinkFormat,
|
|
FileConfig: &FileSinkTypeConfig{
|
|
Path: "./",
|
|
FileName: tmpFile.Name(),
|
|
},
|
|
})
|
|
}
|
|
return c
|
|
}
|
|
|
|
// TestRequestInfo provides a test RequestInfo
|
|
func TestRequestInfo(t testing.TB) *RequestInfo {
|
|
t.Helper()
|
|
return &RequestInfo{
|
|
EventId: "test-event-id",
|
|
Id: "test-request-info",
|
|
Method: "POST",
|
|
Path: "/test/request/info",
|
|
PublicId: "public-id",
|
|
}
|
|
}
|
|
|
|
func testAuth(t testing.TB) *Auth {
|
|
t.Helper()
|
|
return &Auth{
|
|
UserEmail: "test-auth@example.com",
|
|
UserName: "test-auth-user-name",
|
|
}
|
|
}
|
|
|
|
func testRequest(t testing.TB) *Request {
|
|
t.Helper()
|
|
return &Request{
|
|
Operation: "op",
|
|
Endpoint: "/group/<id>",
|
|
Details: &pbs.GetGroupRequest{
|
|
Id: "group-id",
|
|
},
|
|
}
|
|
}
|
|
|
|
func testResponse(t testing.TB) *Response {
|
|
t.Helper()
|
|
return &Response{
|
|
StatusCode: 200,
|
|
Details: &pbs.GetGroupResponse{
|
|
Item: &groups.Group{
|
|
Id: "group-id",
|
|
ScopeId: "org-id",
|
|
Name: &wrapperspb.StringValue{
|
|
Value: "group-name",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// TestWithBroker is an unexported and a test option for passing in an optional broker
|
|
func TestWithBroker(t testing.TB, b broker) Option {
|
|
t.Helper()
|
|
return func(o *options) {
|
|
o.withBroker = b
|
|
}
|
|
}
|
|
|
|
// TestWithObservationSink is a test option
|
|
func TestWithObservationSink(t testing.TB) Option {
|
|
t.Helper()
|
|
return func(o *options) {
|
|
o.withObservationSink = true
|
|
}
|
|
}
|
|
|
|
// TestWithAuditSink is a test option
|
|
func TestWithAuditSink(t testing.TB) Option {
|
|
t.Helper()
|
|
return func(o *options) {
|
|
o.withAuditSink = true
|
|
}
|
|
}
|
|
|
|
// TestWithSysSink is a test option
|
|
func TestWithSysSink(t testing.TB) Option {
|
|
t.Helper()
|
|
return func(o *options) {
|
|
o.withSysSink = true
|
|
}
|
|
}
|
|
|
|
// TestWithStderrSink is a test option
|
|
func TestWithStderrSink(t testing.TB) Option {
|
|
t.Helper()
|
|
return func(o *options) {
|
|
o.withStderrSink = true
|
|
}
|
|
}
|
|
|
|
// withNoDefaultSink is an unexported test option
|
|
func withNoDefaultSink(t testing.TB) Option {
|
|
t.Helper()
|
|
return func(o *options) {
|
|
o.withNoDefaultSink = true
|
|
}
|
|
}
|
|
|
|
// testWithSinkFormat is an unexported and a test option
|
|
func testWithSinkFormat(t testing.TB, fmt SinkFormat) Option {
|
|
t.Helper()
|
|
return func(o *options) {
|
|
o.withSinkFormat = fmt
|
|
}
|
|
}
|
|
|
|
type testMockBroker struct {
|
|
reopened bool
|
|
stopTimeAt time.Time
|
|
registeredNodeIds []eventlogger.NodeID
|
|
successThresholds map[eventlogger.EventType]int
|
|
pipelines []eventlogger.Pipeline
|
|
|
|
errorOnSend error
|
|
}
|
|
|
|
func (b *testMockBroker) Reopen(ctx context.Context) error {
|
|
b.reopened = true
|
|
return nil
|
|
}
|
|
|
|
func (b *testMockBroker) RegisterPipeline(def eventlogger.Pipeline, opt ...eventlogger.Option) error {
|
|
b.pipelines = append(b.pipelines, def)
|
|
return nil
|
|
}
|
|
|
|
func (b *testMockBroker) RemovePipelineAndNodes(ctx context.Context, t eventlogger.EventType, id eventlogger.PipelineID) (bool, error) {
|
|
idx := -1
|
|
for i, p := range b.pipelines {
|
|
if p.PipelineID == id {
|
|
idx = i
|
|
break
|
|
}
|
|
}
|
|
if idx == -1 {
|
|
return true, nil
|
|
}
|
|
b.pipelines = append(b.pipelines[:idx], b.pipelines[idx+1:]...)
|
|
return true, nil
|
|
}
|
|
|
|
func (b *testMockBroker) Send(ctx context.Context, t eventlogger.EventType, payload any) (eventlogger.Status, error) {
|
|
if b.errorOnSend != nil {
|
|
return eventlogger.Status{}, b.errorOnSend
|
|
}
|
|
return eventlogger.Status{}, nil
|
|
}
|
|
|
|
func (b *testMockBroker) StopTimeAt(t time.Time) {
|
|
b.stopTimeAt = t
|
|
}
|
|
|
|
func (b *testMockBroker) RegisterNode(id eventlogger.NodeID, node eventlogger.Node, opt ...eventlogger.Option) error {
|
|
b.registeredNodeIds = append(b.registeredNodeIds, id)
|
|
return nil
|
|
}
|
|
|
|
func (b *testMockBroker) RemoveNode(ctx context.Context, id eventlogger.NodeID) error {
|
|
return nil
|
|
}
|
|
|
|
func (b *testMockBroker) SetSuccessThreshold(t eventlogger.EventType, successThreshold int) error {
|
|
if b.successThresholds == nil {
|
|
b.successThresholds = map[eventlogger.EventType]int{}
|
|
}
|
|
b.successThresholds[t] = successThreshold
|
|
return nil
|
|
}
|
|
|
|
func testLogger(t *testing.T, testLock hclog.Locker) hclog.Logger {
|
|
t.Helper()
|
|
return hclog.New(&hclog.LoggerOptions{
|
|
Mutex: testLock,
|
|
Name: "test",
|
|
JSONFormat: true,
|
|
})
|
|
}
|
|
|
|
func testWorkerStatus(t testing.TB) *services.StatusRequest {
|
|
t.Helper()
|
|
return &services.StatusRequest{
|
|
Jobs: []*services.JobStatus{
|
|
{Job: &services.Job{
|
|
Type: 1,
|
|
JobInfo: nil,
|
|
}},
|
|
},
|
|
UpdateTags: false,
|
|
WorkerStatus: &servers.ServerWorkerStatus{
|
|
PublicId: "testID",
|
|
Name: "w_1234567890",
|
|
Description: "A default worker created in",
|
|
Address: "127.0.0.1:9202",
|
|
Tags: []*servers.TagPair{
|
|
{
|
|
Key: "type",
|
|
Value: "dev",
|
|
},
|
|
},
|
|
KeyId: "ovary-valid-curler-scrambled-glutinous-alias-rework-debit",
|
|
ReleaseVersion: "Boundary v0.13.1",
|
|
OperationalState: "active",
|
|
},
|
|
}
|
|
}
|
|
|
|
func testWorkerStatusObservable(t testing.TB) *services.StatusRequest {
|
|
t.Helper()
|
|
return &services.StatusRequest{
|
|
Jobs: []*services.JobStatus{
|
|
{Job: &services.Job{}},
|
|
},
|
|
WorkerStatus: &servers.ServerWorkerStatus{
|
|
PublicId: "testID",
|
|
ReleaseVersion: "Boundary v0.13.1",
|
|
OperationalState: "active",
|
|
},
|
|
}
|
|
}
|