feat(http): add audit log

pull/13552/head
Kelvin Wang 2019-04-19 15:46:58 -04:00
parent 184ed7120e
commit f75f27c0bd
28 changed files with 148 additions and 84 deletions

View File

@ -29,7 +29,7 @@ type Client struct {
IDGenerator platform.IDGenerator IDGenerator platform.IDGenerator
TokenGenerator platform.TokenGenerator TokenGenerator platform.TokenGenerator
time func() time.Time platform.TimeGenerator
} }
// NewClient returns an instance of a Client. // NewClient returns an instance of a Client.
@ -38,7 +38,7 @@ func NewClient() *Client {
Logger: zap.NewNop(), Logger: zap.NewNop(),
IDGenerator: snowflake.NewIDGenerator(), IDGenerator: snowflake.NewIDGenerator(),
TokenGenerator: rand.NewTokenGenerator(64), TokenGenerator: rand.NewTokenGenerator(64),
time: time.Now, TimeGenerator: platform.RealTimeGenerator{},
} }
} }
@ -53,12 +53,6 @@ func (c *Client) WithLogger(l *zap.Logger) {
c.Logger = l c.Logger = l
} }
// WithTime sets the function for computing the current time. Used for updating meta data
// about objects stored. Should only be used in tests for mocking.
func (c *Client) WithTime(fn func() time.Time) {
c.time = fn
}
// Open / create boltDB file. // Open / create boltDB file.
func (c *Client) Open(ctx context.Context) error { func (c *Client) Open(ctx context.Context) error {
// Ensure the required directory structure exists. // Ensure the required directory structure exists.

View File

@ -676,5 +676,5 @@ func (c *Client) appendBucketEventToLog(ctx context.Context, tx *bolt.Tx, id pla
return err return err
} }
return c.addLogEntry(ctx, tx, k, v, c.time()) return c.addLogEntry(ctx, tx, k, v, c.Now())
} }

View File

@ -286,7 +286,7 @@ func (c *Client) CreateDashboard(ctx context.Context, d *platform.Dashboard) err
} }
// TODO(desa): don't populate this here. use the first/last methods of the oplog to get meta fields. // TODO(desa): don't populate this here. use the first/last methods of the oplog to get meta fields.
d.Meta.CreatedAt = c.time() d.Meta.CreatedAt = c.Now()
return c.putDashboardWithMeta(ctx, tx, d) return c.putDashboardWithMeta(ctx, tx, d)
}) })
@ -698,7 +698,7 @@ func (c *Client) putDashboard(ctx context.Context, tx *bolt.Tx, d *platform.Dash
func (c *Client) putDashboardWithMeta(ctx context.Context, tx *bolt.Tx, d *platform.Dashboard) error { func (c *Client) putDashboardWithMeta(ctx context.Context, tx *bolt.Tx, d *platform.Dashboard) error {
// TODO(desa): don't populate this here. use the first/last methods of the oplog to get meta fields. // TODO(desa): don't populate this here. use the first/last methods of the oplog to get meta fields.
d.Meta.UpdatedAt = c.time() d.Meta.UpdatedAt = c.Now()
return c.putDashboard(ctx, tx, d) return c.putDashboard(ctx, tx, d)
} }
@ -907,5 +907,5 @@ func (c *Client) appendDashboardEventToLog(ctx context.Context, tx *bolt.Tx, id
return err return err
} }
return c.addLogEntry(ctx, tx, k, v, c.time()) return c.addLogEntry(ctx, tx, k, v, c.Now())
} }

View File

@ -3,7 +3,6 @@ package bolt_test
import ( import (
"context" "context"
"testing" "testing"
"time"
platform "github.com/influxdata/influxdb" platform "github.com/influxdata/influxdb"
"github.com/influxdata/influxdb/bolt" "github.com/influxdata/influxdb/bolt"
@ -16,12 +15,13 @@ func initDashboardService(f platformtesting.DashboardFields, t *testing.T) (plat
t.Fatalf("failed to create new bolt client: %v", err) t.Fatalf("failed to create new bolt client: %v", err)
} }
if f.NowFn == nil { if f.TimeGenerator == nil {
f.NowFn = time.Now f.TimeGenerator = platform.RealTimeGenerator{}
} }
c.IDGenerator = f.IDGenerator c.IDGenerator = f.IDGenerator
c.WithTime(f.NowFn) c.TimeGenerator = f.TimeGenerator
ctx := context.TODO() ctx := context.TODO()
for _, b := range f.Dashboards { for _, b := range f.Dashboards {
if err := c.PutDashboard(ctx, b); err != nil { if err := c.PutDashboard(ctx, b); err != nil {

View File

@ -501,7 +501,7 @@ func (c *Client) appendOrganizationEventToLog(ctx context.Context, tx *bolt.Tx,
return err return err
} }
return c.addLogEntry(ctx, tx, k, v, c.time()) return c.addLogEntry(ctx, tx, k, v, c.Now())
} }
func (c *Client) FindResourceOrganizationID(ctx context.Context, rt influxdb.ResourceType, id influxdb.ID) (influxdb.ID, error) { func (c *Client) FindResourceOrganizationID(ctx context.Context, rt influxdb.ResourceType, id influxdb.ID) (influxdb.ID, error) {

View File

@ -476,5 +476,5 @@ func (c *Client) appendUserEventToLog(ctx context.Context, tx *bolt.Tx, id platf
return err return err
} }
return c.addLogEntry(ctx, tx, k, v, c.time()) return c.addLogEntry(ctx, tx, k, v, c.Now())
} }

View File

@ -45,7 +45,7 @@ import (
_ "github.com/influxdata/influxdb/tsdb/tsm1" // needed for tsm1 _ "github.com/influxdata/influxdb/tsdb/tsm1" // needed for tsm1
"github.com/influxdata/influxdb/vault" "github.com/influxdata/influxdb/vault"
pzap "github.com/influxdata/influxdb/zap" pzap "github.com/influxdata/influxdb/zap"
"github.com/opentracing/opentracing-go" opentracing "github.com/opentracing/opentracing-go"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
jaegerconfig "github.com/uber/jaeger-client-go/config" jaegerconfig "github.com/uber/jaeger-client-go/config"

25
crud_log.go Normal file
View File

@ -0,0 +1,25 @@
package influxdb
import (
"time"
)
// CRUDLog is the struct to store crud related ops.
type CRUDLog struct {
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
// TimeGenerator represents a generator for now.
type TimeGenerator interface {
// Now creates the generated time.
Now() time.Time
}
// RealTimeGenerator will generate the real time.
type RealTimeGenerator struct{}
// Now returns the current time.
func (g RealTimeGenerator) Now() time.Time {
return time.Now()
}

View File

@ -28,6 +28,7 @@ type DocumentMeta struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
Version string `json:"version,omitempty"` Version string `json:"version,omitempty"`
CRUDLog
} }
// DocumentStore is used to perform CRUD operations on documents. It follows an options // DocumentStore is used to perform CRUD operations on documents. It follows an options

View File

@ -1363,7 +1363,7 @@ func initDashboardService(f platformtesting.DashboardFields, t *testing.T) (plat
t.Helper() t.Helper()
svc := inmem.NewService() svc := inmem.NewService()
svc.IDGenerator = f.IDGenerator svc.IDGenerator = f.IDGenerator
svc.WithTime(f.NowFn) svc.TimeGenerator = f.TimeGenerator
ctx := context.Background() ctx := context.Background()
for _, d := range f.Dashboards { for _, d := range f.Dashboards {
if err := svc.PutDashboard(ctx, d); err != nil { if err := svc.PutDashboard(ctx, d); err != nil {

View File

@ -140,6 +140,8 @@ func (h *DocumentHandler) handlePostDocument(w http.ResponseWriter, r *http.Requ
return return
} }
h.Logger.Info("document created")
if err := encodeResponse(ctx, w, http.StatusCreated, newDocumentResponse(req.Namespace, req.Document)); err != nil { if err := encodeResponse(ctx, w, http.StatusCreated, newDocumentResponse(req.Namespace, req.Document)); err != nil {
logEncodingError(h.Logger, r, err) logEncodingError(h.Logger, r, err)
return return
@ -459,6 +461,8 @@ func (h *DocumentHandler) handleDeleteDocument(w http.ResponseWriter, r *http.Re
return return
} }
h.Logger.Info("document deleted")
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
} }
@ -526,6 +530,8 @@ func (h *DocumentHandler) handlePutDocument(w http.ResponseWriter, r *http.Reque
return return
} }
h.Logger.Info("document updated")
ds, err := s.FindDocuments(ctx, influxdb.WhereID(req.Document.ID), influxdb.IncludeContent) ds, err := s.FindDocuments(ctx, influxdb.WhereID(req.Document.ID), influxdb.IncludeContent)
if err != nil { if err != nil {
EncodeError(ctx, err, w) EncodeError(ctx, err, w)

View File

@ -8,6 +8,7 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"time"
"github.com/influxdata/influxdb" "github.com/influxdata/influxdb"
pcontext "github.com/influxdata/influxdb/context" pcontext "github.com/influxdata/influxdb/context"
@ -43,6 +44,9 @@ var (
label3MappingJSON, _ = json.Marshal(influxdb.LabelMapping{ label3MappingJSON, _ = json.Marshal(influxdb.LabelMapping{
LabelID: label3ID, LabelID: label3ID,
}) })
mockGen = mock.TimeGenerator{
FakeValue: time.Date(2006, 5, 24, 1, 2, 3, 4, time.UTC),
}
doc1 = influxdb.Document{ doc1 = influxdb.Document{
ID: doc1ID, ID: doc1ID,
Meta: influxdb.DocumentMeta{ Meta: influxdb.DocumentMeta{
@ -127,6 +131,8 @@ var (
"meta": { "meta": {
"name": "doc1", "name": "doc1",
"type": "typ1", "type": "typ1",
"createdAt": "0001-01-01T00:00:00Z",
"updatedAt": "0001-01-01T00:00:00Z",
"description": "desc1" "description": "desc1"
} }
}, },
@ -137,7 +143,9 @@ var (
}, },
"content": "content2", "content": "content2",
"meta": { "meta": {
"name": "doc2" "name": "doc2",
"createdAt": "0001-01-01T00:00:00Z",
"updatedAt": "0001-01-01T00:00:00Z"
} }
} }
] ]
@ -427,6 +435,7 @@ func TestService_handlePostDocumentLabel(t *testing.T) {
DocumentService: &mock.DocumentService{ DocumentService: &mock.DocumentService{
FindDocumentStoreFn: func(context.Context, string) (influxdb.DocumentStore, error) { FindDocumentStoreFn: func(context.Context, string) (influxdb.DocumentStore, error) {
return &mock.DocumentStore{ return &mock.DocumentStore{
TimeGenerator: mockGen,
FindDocumentsFn: func(ctx context.Context, opts ...influxdb.DocumentFindOptions) ([]*influxdb.Document, error) { FindDocumentsFn: func(ctx context.Context, opts ...influxdb.DocumentFindOptions) ([]*influxdb.Document, error) {
return []*influxdb.Document{&doc4}, nil return []*influxdb.Document{&doc4}, nil
}, },
@ -801,7 +810,9 @@ func TestService_handlePostDocuments(t *testing.T) {
DocumentService: &mock.DocumentService{ DocumentService: &mock.DocumentService{
FindDocumentStoreFn: func(context.Context, string) (influxdb.DocumentStore, error) { FindDocumentStoreFn: func(context.Context, string) (influxdb.DocumentStore, error) {
return &mock.DocumentStore{ return &mock.DocumentStore{
TimeGenerator: mockGen,
CreateDocumentFn: func(ctx context.Context, d *influxdb.Document, opts ...influxdb.DocumentOptions) error { CreateDocumentFn: func(ctx context.Context, d *influxdb.Document, opts ...influxdb.DocumentOptions) error {
d.Meta.CreatedAt = mockGen.Now()
return nil return nil
}, },
}, nil }, nil
@ -826,7 +837,9 @@ func TestService_handlePostDocuments(t *testing.T) {
"self": "/api/v2/documents/template/020f755c3c082014" "self": "/api/v2/documents/template/020f755c3c082014"
}, },
"meta": { "meta": {
"name": "doc5" "name": "doc5",
"createdAt": "2006-05-24T01:02:03.000000004Z",
"updatedAt": "0001-01-01T00:00:00Z"
}}`, }}`,
}, },
}, },
@ -836,8 +849,10 @@ func TestService_handlePostDocuments(t *testing.T) {
DocumentService: &mock.DocumentService{ DocumentService: &mock.DocumentService{
FindDocumentStoreFn: func(context.Context, string) (influxdb.DocumentStore, error) { FindDocumentStoreFn: func(context.Context, string) (influxdb.DocumentStore, error) {
return &mock.DocumentStore{ return &mock.DocumentStore{
TimeGenerator: mockGen,
CreateDocumentFn: func(ctx context.Context, d *influxdb.Document, opts ...influxdb.DocumentOptions) error { CreateDocumentFn: func(ctx context.Context, d *influxdb.Document, opts ...influxdb.DocumentOptions) error {
d.Labels = []*influxdb.Label{&label1, &label2} d.Labels = []*influxdb.Label{&label1, &label2}
d.Meta.CreatedAt = mockGen.Now()
return nil return nil
}, },
}, nil }, nil
@ -877,7 +892,9 @@ func TestService_handlePostDocuments(t *testing.T) {
"name": "l2" "name": "l2"
}], }],
"meta": { "meta": {
"name": "doc6" "name": "doc6",
"createdAt": "2006-05-24T01:02:03.000000004Z",
"updatedAt": "0001-01-01T00:00:00Z"
}}`, }}`,
}, },
}, },

View File

@ -6983,6 +6983,14 @@ components:
type: string type: string
version: version:
type: string type: string
createdAt:
type: string
format: date-time
readOnly: true
updatedAt:
type: string
format: date-time
readOnly: true
required: required:
- name - name
- version - version

View File

@ -129,7 +129,7 @@ func (s *Service) FindDashboards(ctx context.Context, filter platform.DashboardF
// CreateDashboard implements platform.DashboardService interface. // CreateDashboard implements platform.DashboardService interface.
func (s *Service) CreateDashboard(ctx context.Context, d *platform.Dashboard) error { func (s *Service) CreateDashboard(ctx context.Context, d *platform.Dashboard) error {
d.ID = s.IDGenerator.ID() d.ID = s.IDGenerator.ID()
d.Meta.CreatedAt = s.time() d.Meta.CreatedAt = s.Now()
err := s.PutDashboardWithMeta(ctx, d) err := s.PutDashboardWithMeta(ctx, d)
if err != nil { if err != nil {
return &platform.Error{ return &platform.Error{
@ -160,7 +160,7 @@ func (s *Service) PutCellView(ctx context.Context, cell *platform.Cell) error {
// PutDashboardWithMeta sets a dashboard while updating the meta field of a dashboard. // PutDashboardWithMeta sets a dashboard while updating the meta field of a dashboard.
func (s *Service) PutDashboardWithMeta(ctx context.Context, d *platform.Dashboard) error { func (s *Service) PutDashboardWithMeta(ctx context.Context, d *platform.Dashboard) error {
d.Meta.UpdatedAt = s.time() d.Meta.UpdatedAt = s.Now()
return s.PutDashboard(ctx, d) return s.PutDashboard(ctx, d)
} }

View File

@ -11,8 +11,8 @@ import (
func initDashboardService(f platformtesting.DashboardFields, t *testing.T) (platform.DashboardService, string, func()) { func initDashboardService(f platformtesting.DashboardFields, t *testing.T) (platform.DashboardService, string, func()) {
s := NewService() s := NewService()
s.IDGenerator = f.IDGenerator s.IDGenerator = f.IDGenerator
s.TimeGenerator = f.TimeGenerator
ctx := context.Background() ctx := context.Background()
s.WithTime(f.NowFn)
for _, b := range f.Dashboards { for _, b := range f.Dashboards {
if err := s.PutDashboard(ctx, b); err != nil { if err := s.PutDashboard(ctx, b); err != nil {
t.Fatalf("failed to populate Dashboards") t.Fatalf("failed to populate Dashboards")

View File

@ -3,7 +3,6 @@ package inmem
import ( import (
"context" "context"
"sync" "sync"
"time"
platform "github.com/influxdata/influxdb" platform "github.com/influxdata/influxdb"
"github.com/influxdata/influxdb/rand" "github.com/influxdata/influxdb/rand"
@ -35,7 +34,7 @@ type Service struct {
TokenGenerator platform.TokenGenerator TokenGenerator platform.TokenGenerator
IDGenerator platform.IDGenerator IDGenerator platform.IDGenerator
time func() time.Time platform.TimeGenerator
} }
// NewService creates an instance of a Service. // NewService creates an instance of a Service.
@ -43,18 +42,12 @@ func NewService() *Service {
s := &Service{ s := &Service{
TokenGenerator: rand.NewTokenGenerator(64), TokenGenerator: rand.NewTokenGenerator(64),
IDGenerator: snowflake.NewIDGenerator(), IDGenerator: snowflake.NewIDGenerator(),
time: time.Now, TimeGenerator: platform.RealTimeGenerator{},
} }
s.initializeSources(context.TODO()) s.initializeSources(context.TODO())
return s return s
} }
// WithTime sets the function for computing the current time. Used for updating meta data
// about objects stored. Should only be used in tests for mocking.
func (s *Service) WithTime(fn func() time.Time) {
s.time = fn
}
// Flush removes all data from the in-memory store // Flush removes all data from the in-memory store
func (s *Service) Flush() { func (s *Service) Flush() {
s.flush(&s.authorizationKV) s.flush(&s.authorizationKV)

View File

@ -759,7 +759,7 @@ func (s *Service) appendBucketEventToLog(ctx context.Context, tx Tx, id influxdb
return err return err
} }
return s.addLogEntry(ctx, tx, k, v, s.time()) return s.addLogEntry(ctx, tx, k, v, s.Now())
} }
// UnexpectedBucketError is used when the error comes from an internal system. // UnexpectedBucketError is used when the error comes from an internal system.

View File

@ -301,7 +301,7 @@ func (s *Service) CreateDashboard(ctx context.Context, d *influxdb.Dashboard) er
} }
// TODO(desa): don't populate this here. use the first/last methods of the oplog to get meta fields. // TODO(desa): don't populate this here. use the first/last methods of the oplog to get meta fields.
d.Meta.CreatedAt = s.time() d.Meta.CreatedAt = s.Now()
if err := s.putDashboardWithMeta(ctx, tx, d); err != nil { if err := s.putDashboardWithMeta(ctx, tx, d); err != nil {
return err return err
@ -752,7 +752,7 @@ func (s *Service) putDashboard(ctx context.Context, tx Tx, d *influxdb.Dashboard
func (s *Service) putDashboardWithMeta(ctx context.Context, tx Tx, d *influxdb.Dashboard) error { func (s *Service) putDashboardWithMeta(ctx context.Context, tx Tx, d *influxdb.Dashboard) error {
// TODO(desa): don't populate this here. use the first/last methods of the oplog to get meta fields. // TODO(desa): don't populate this here. use the first/last methods of the oplog to get meta fields.
d.Meta.UpdatedAt = s.time() d.Meta.UpdatedAt = s.Now()
return s.putDashboard(ctx, tx, d) return s.putDashboard(ctx, tx, d)
} }
@ -972,5 +972,5 @@ func (s *Service) appendDashboardEventToLog(ctx context.Context, tx Tx, id influ
return err return err
} }
return s.addLogEntry(ctx, tx, k, v, s.time()) return s.addLogEntry(ctx, tx, k, v, s.Now())
} }

View File

@ -3,7 +3,6 @@ package kv_test
import ( import (
"context" "context"
"testing" "testing"
"time"
"github.com/influxdata/influxdb" "github.com/influxdata/influxdb"
"github.com/influxdata/influxdb/kv" "github.com/influxdata/influxdb/kv"
@ -46,12 +45,12 @@ func initInmemDashboardService(f influxdbtesting.DashboardFields, t *testing.T)
func initDashboardService(s kv.Store, f influxdbtesting.DashboardFields, t *testing.T) (influxdb.DashboardService, string, func()) { func initDashboardService(s kv.Store, f influxdbtesting.DashboardFields, t *testing.T) (influxdb.DashboardService, string, func()) {
if f.NowFn == nil { if f.TimeGenerator == nil {
f.NowFn = time.Now f.TimeGenerator = influxdb.RealTimeGenerator{}
} }
svc := kv.NewService(s) svc := kv.NewService(s)
svc.IDGenerator = f.IDGenerator svc.IDGenerator = f.IDGenerator
svc.WithTime(f.NowFn) svc.TimeGenerator = f.TimeGenerator
ctx := context.Background() ctx := context.Background()
if err := svc.Initialize(ctx); err != nil { if err := svc.Initialize(ctx); err != nil {

View File

@ -348,7 +348,7 @@ func (i *DocumentIndex) GetAccessorsDocuments(ownerType string, ownerID influxdb
func (s *Service) createDocument(ctx context.Context, tx Tx, ns string, d *influxdb.Document) error { func (s *Service) createDocument(ctx context.Context, tx Tx, ns string, d *influxdb.Document) error {
d.ID = s.IDGenerator.ID() d.ID = s.IDGenerator.ID()
d.Meta.CreatedAt = s.Now()
if err := s.putDocument(ctx, tx, ns, d); err != nil { if err := s.putDocument(ctx, tx, ns, d); err != nil {
return err return err
} }
@ -357,7 +357,7 @@ func (s *Service) createDocument(ctx context.Context, tx Tx, ns string, d *influ
} }
func (s *Service) putDocument(ctx context.Context, tx Tx, ns string, d *influxdb.Document) error { func (s *Service) putDocument(ctx context.Context, tx Tx, ns string, d *influxdb.Document) error {
if err := s.putDocumentMeta(ctx, tx, ns, d.ID, &d.Meta); err != nil { if err := s.putDocumentMeta(ctx, tx, ns, d.ID, d.Meta); err != nil {
return err return err
} }
@ -397,7 +397,7 @@ func (s *Service) putDocumentContent(ctx context.Context, tx Tx, ns string, id i
return s.putAtID(ctx, tx, path.Join(ns, documentContentBucket), id, data) return s.putAtID(ctx, tx, path.Join(ns, documentContentBucket), id, data)
} }
func (s *Service) putDocumentMeta(ctx context.Context, tx Tx, ns string, id influxdb.ID, m *influxdb.DocumentMeta) error { func (s *Service) putDocumentMeta(ctx context.Context, tx Tx, ns string, id influxdb.ID, m influxdb.DocumentMeta) error {
return s.putAtID(ctx, tx, path.Join(ns, documentMetaBucket), id, m) return s.putAtID(ctx, tx, path.Join(ns, documentMetaBucket), id, m)
} }
@ -735,7 +735,7 @@ func (s *DocumentStore) UpdateDocument(ctx context.Context, d *influxdb.Document
func (s *Service) updateDocument(ctx context.Context, tx Tx, ns string, d *influxdb.Document) error { func (s *Service) updateDocument(ctx context.Context, tx Tx, ns string, d *influxdb.Document) error {
// TODO(desa): deindex meta // TODO(desa): deindex meta
d.Meta.UpdatedAt = s.Now()
if err := s.putDocument(ctx, tx, ns, d); err != nil { if err := s.putDocument(ctx, tx, ns, d); err != nil {
return err return err
} }

View File

@ -108,7 +108,10 @@ func (s *Service) initializeKVLog(ctx context.Context, tx Tx) error {
return nil return nil
} }
var errKeyValueLogBoundsNotFound = fmt.Errorf("oplog not found") var errKeyValueLogBoundsNotFound = &platform.Error{
Code: platform.ENotFound,
Msg: "oplog not found",
}
func (s *Service) getKeyValueLogBounds(ctx context.Context, tx Tx, key []byte) (*keyValueLogBounds, error) { func (s *Service) getKeyValueLogBounds(ctx context.Context, tx Tx, key []byte) (*keyValueLogBounds, error) {
k := encodeKeyValueIndexKey(key) k := encodeKeyValueIndexKey(key)
@ -381,7 +384,7 @@ func (s *Service) LastLogEntry(ctx context.Context, k []byte) ([]byte, time.Time
func (s *Service) firstLogEntry(ctx context.Context, tx Tx, k []byte) ([]byte, time.Time, error) { func (s *Service) firstLogEntry(ctx context.Context, tx Tx, k []byte) ([]byte, time.Time, error) {
bounds, err := s.getKeyValueLogBounds(ctx, tx, k) bounds, err := s.getKeyValueLogBounds(ctx, tx, k)
if err != nil { if err != nil {
return nil, bounds.StartTime(), err return nil, time.Time{}, err
} }
return s.getLogEntry(ctx, tx, k, bounds.StartTime()) return s.getLogEntry(ctx, tx, k, bounds.StartTime())
@ -390,7 +393,7 @@ func (s *Service) firstLogEntry(ctx context.Context, tx Tx, k []byte) ([]byte, t
func (s *Service) lastLogEntry(ctx context.Context, tx Tx, k []byte) ([]byte, time.Time, error) { func (s *Service) lastLogEntry(ctx context.Context, tx Tx, k []byte) ([]byte, time.Time, error) {
bounds, err := s.getKeyValueLogBounds(ctx, tx, k) bounds, err := s.getKeyValueLogBounds(ctx, tx, k)
if err != nil { if err != nil {
return nil, bounds.StopTime(), err return nil, time.Time{}, err
} }
return s.getLogEntry(ctx, tx, k, bounds.StopTime()) return s.getLogEntry(ctx, tx, k, bounds.StopTime())

View File

@ -570,7 +570,7 @@ func (s *Service) appendOrganizationEventToLog(ctx context.Context, tx Tx, id in
return err return err
} }
return s.addLogEntry(ctx, tx, k, v, s.time()) return s.addLogEntry(ctx, tx, k, v, s.Now())
} }
// FindResourceOrganizationID is used to find the organization that a resource belongs to five the id of a resource and a resource type. // FindResourceOrganizationID is used to find the organization that a resource belongs to five the id of a resource and a resource type.

View File

@ -2,7 +2,6 @@ package kv
import ( import (
"context" "context"
"time"
"go.uber.org/zap" "go.uber.org/zap"
@ -25,9 +24,8 @@ type Service struct {
IDGenerator influxdb.IDGenerator IDGenerator influxdb.IDGenerator
TokenGenerator influxdb.TokenGenerator TokenGenerator influxdb.TokenGenerator
Hash Crypt influxdb.TimeGenerator
Hash Crypt
time func() time.Time
} }
// NewService returns an instance of a Service. // NewService returns an instance of a Service.
@ -38,7 +36,7 @@ func NewService(kv Store) *Service {
TokenGenerator: rand.NewTokenGenerator(64), TokenGenerator: rand.NewTokenGenerator(64),
Hash: &Bcrypt{}, Hash: &Bcrypt{},
kv: kv, kv: kv,
time: time.Now, TimeGenerator: influxdb.RealTimeGenerator{},
} }
} }
@ -117,12 +115,6 @@ func (s *Service) Initialize(ctx context.Context) error {
}) })
} }
// WithTime sets the function for computing the current time. Used for updating meta data
// about objects stored. Should only be used in tests for mocking.
func (s *Service) WithTime(fn func() time.Time) {
s.time = fn
}
// WithStore sets kv store for the service. // WithStore sets kv store for the service.
// Should only be used in tests for mocking. // Should only be used in tests for mocking.
func (s *Service) WithStore(store Store) { func (s *Service) WithStore(store Store) {

View File

@ -515,7 +515,7 @@ func (s *Service) appendUserEventToLog(ctx context.Context, tx Tx, id influxdb.I
return err return err
} }
return s.addLogEntry(ctx, tx, k, v, s.time()) return s.addLogEntry(ctx, tx, k, v, s.Now())
} }
var ( var (

View File

@ -38,6 +38,7 @@ func NewDocumentService() *DocumentService {
// DocumentStore is the mocked document store. // DocumentStore is the mocked document store.
type DocumentStore struct { type DocumentStore struct {
TimeGenerator TimeGenerator
CreateDocumentFn func(ctx context.Context, d *influxdb.Document, opts ...influxdb.DocumentOptions) error CreateDocumentFn func(ctx context.Context, d *influxdb.Document, opts ...influxdb.DocumentOptions) error
UpdateDocumentFn func(ctx context.Context, d *influxdb.Document, opts ...influxdb.DocumentOptions) error UpdateDocumentFn func(ctx context.Context, d *influxdb.Document, opts ...influxdb.DocumentOptions) error
FindDocumentsFn func(ctx context.Context, opts ...influxdb.DocumentFindOptions) ([]*influxdb.Document, error) FindDocumentsFn func(ctx context.Context, opts ...influxdb.DocumentFindOptions) ([]*influxdb.Document, error)

View File

@ -2,6 +2,7 @@ package mock
import ( import (
"testing" "testing"
"time"
platform "github.com/influxdata/influxdb" platform "github.com/influxdata/influxdb"
) )
@ -47,3 +48,13 @@ type TokenGenerator struct {
func (g TokenGenerator) Token() (string, error) { func (g TokenGenerator) Token() (string, error) {
return g.TokenFn() return g.TokenFn()
} }
// TimeGenerator stores a fake value of time.
type TimeGenerator struct {
FakeValue time.Time
}
// Now will return the FakeValue stored in the struct.
func (g TimeGenerator) Now() time.Time {
return g.FakeValue
}

View File

@ -31,10 +31,10 @@ var dashboardCmpOptions = cmp.Options{
// DashboardFields will include the IDGenerator, and dashboards // DashboardFields will include the IDGenerator, and dashboards
type DashboardFields struct { type DashboardFields struct {
IDGenerator platform.IDGenerator IDGenerator platform.IDGenerator
NowFn func() time.Time TimeGenerator platform.TimeGenerator
Dashboards []*platform.Dashboard Dashboards []*platform.Dashboard
Views []*platform.View Views []*platform.View
} }
// DashboardService tests all the service functions. // DashboardService tests all the service functions.
@ -125,7 +125,7 @@ func CreateDashboard(
return MustIDBase16(dashTwoID) return MustIDBase16(dashTwoID)
}, },
}, },
NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC)},
Dashboards: []*platform.Dashboard{ Dashboards: []*platform.Dashboard{
{ {
ID: MustIDBase16(dashOneID), ID: MustIDBase16(dashOneID),
@ -168,7 +168,7 @@ func CreateDashboard(
return MustIDBase16(dashTwoID) return MustIDBase16(dashTwoID)
}, },
}, },
NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC)},
Dashboards: []*platform.Dashboard{ Dashboards: []*platform.Dashboard{
{ {
ID: MustIDBase16(dashOneID), ID: MustIDBase16(dashOneID),
@ -253,7 +253,7 @@ func AddDashboardCell(
return MustIDBase16(dashTwoID) return MustIDBase16(dashTwoID)
}, },
}, },
NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC)},
Dashboards: []*platform.Dashboard{ Dashboards: []*platform.Dashboard{
{ {
ID: MustIDBase16(dashOneID), ID: MustIDBase16(dashOneID),
@ -296,7 +296,7 @@ func AddDashboardCell(
{ {
name: "add cell with no id", name: "add cell with no id",
fields: DashboardFields{ fields: DashboardFields{
NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC)},
IDGenerator: &mock.IDGenerator{ IDGenerator: &mock.IDGenerator{
IDFn: func() platform.ID { IDFn: func() platform.ID {
return MustIDBase16(dashTwoID) return MustIDBase16(dashTwoID)
@ -342,7 +342,7 @@ func AddDashboardCell(
{ {
name: "add cell with id not exist", name: "add cell with id not exist",
fields: DashboardFields{ fields: DashboardFields{
NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC)},
IDGenerator: &mock.IDGenerator{ IDGenerator: &mock.IDGenerator{
IDFn: func() platform.ID { IDFn: func() platform.ID {
return MustIDBase16(dashTwoID) return MustIDBase16(dashTwoID)
@ -1059,7 +1059,7 @@ func UpdateDashboard(
{ {
name: "update name", name: "update name",
fields: DashboardFields{ fields: DashboardFields{
NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC)},
Dashboards: []*platform.Dashboard{ Dashboards: []*platform.Dashboard{
{ {
ID: MustIDBase16(dashOneID), ID: MustIDBase16(dashOneID),
@ -1091,7 +1091,7 @@ func UpdateDashboard(
{ {
name: "update description", name: "update description",
fields: DashboardFields{ fields: DashboardFields{
NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC)},
Dashboards: []*platform.Dashboard{ Dashboards: []*platform.Dashboard{
{ {
ID: MustIDBase16(dashOneID), ID: MustIDBase16(dashOneID),
@ -1124,7 +1124,7 @@ func UpdateDashboard(
{ {
name: "update description and name", name: "update description and name",
fields: DashboardFields{ fields: DashboardFields{
NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC)},
Dashboards: []*platform.Dashboard{ Dashboards: []*platform.Dashboard{
{ {
ID: MustIDBase16(dashOneID), ID: MustIDBase16(dashOneID),
@ -1158,7 +1158,7 @@ func UpdateDashboard(
{ {
name: "update with id not exist", name: "update with id not exist",
fields: DashboardFields{ fields: DashboardFields{
NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC)},
Dashboards: []*platform.Dashboard{ Dashboards: []*platform.Dashboard{
{ {
ID: MustIDBase16(dashOneID), ID: MustIDBase16(dashOneID),
@ -1234,7 +1234,7 @@ func RemoveDashboardCell(
{ {
name: "basic remove cell", name: "basic remove cell",
fields: DashboardFields{ fields: DashboardFields{
NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC)},
IDGenerator: &mock.IDGenerator{ IDGenerator: &mock.IDGenerator{
IDFn: func() platform.ID { IDFn: func() platform.ID {
return MustIDBase16(dashTwoID) return MustIDBase16(dashTwoID)
@ -1332,7 +1332,7 @@ func UpdateDashboardCell(
{ {
name: "basic update cell", name: "basic update cell",
fields: DashboardFields{ fields: DashboardFields{
NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC)},
IDGenerator: &mock.IDGenerator{ IDGenerator: &mock.IDGenerator{
IDFn: func() platform.ID { IDFn: func() platform.ID {
return MustIDBase16(dashTwoID) return MustIDBase16(dashTwoID)
@ -1388,7 +1388,7 @@ func UpdateDashboardCell(
{ {
name: "invalid cell update without attribute", name: "invalid cell update without attribute",
fields: DashboardFields{ fields: DashboardFields{
NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC)},
IDGenerator: &mock.IDGenerator{ IDGenerator: &mock.IDGenerator{
IDFn: func() platform.ID { IDFn: func() platform.ID {
return MustIDBase16(dashTwoID) return MustIDBase16(dashTwoID)
@ -1441,7 +1441,7 @@ func UpdateDashboardCell(
{ {
name: "invalid cell update cell id not exist", name: "invalid cell update cell id not exist",
fields: DashboardFields{ fields: DashboardFields{
NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC)},
IDGenerator: &mock.IDGenerator{ IDGenerator: &mock.IDGenerator{
IDFn: func() platform.ID { IDFn: func() platform.ID {
return MustIDBase16(dashTwoID) return MustIDBase16(dashTwoID)
@ -1539,7 +1539,7 @@ func ReplaceDashboardCells(
{ {
name: "basic replace cells", name: "basic replace cells",
fields: DashboardFields{ fields: DashboardFields{
NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC)},
IDGenerator: &mock.IDGenerator{ IDGenerator: &mock.IDGenerator{
IDFn: func() platform.ID { IDFn: func() platform.ID {
return MustIDBase16(dashTwoID) return MustIDBase16(dashTwoID)
@ -1620,7 +1620,7 @@ func ReplaceDashboardCells(
{ {
name: "try to add a cell that didn't previously exist", name: "try to add a cell that didn't previously exist",
fields: DashboardFields{ fields: DashboardFields{
NowFn: func() time.Time { return time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC) }, TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2009, time.November, 10, 24, 0, 0, 0, time.UTC)},
IDGenerator: &mock.IDGenerator{ IDGenerator: &mock.IDGenerator{
IDFn: func() platform.ID { IDFn: func() platform.ID {
return MustIDBase16(dashTwoID) return MustIDBase16(dashTwoID)

View File

@ -5,11 +5,13 @@ import (
"context" "context"
"sort" "sort"
"testing" "testing"
"time"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts" "github.com/google/go-cmp/cmp/cmpopts"
"github.com/influxdata/influxdb" "github.com/influxdata/influxdb"
"github.com/influxdata/influxdb/kv" "github.com/influxdata/influxdb/kv"
"github.com/influxdata/influxdb/mock"
) )
// NewDocumentIntegrationTest will test the documents related funcs. // NewDocumentIntegrationTest will test the documents related funcs.
@ -17,10 +19,13 @@ func NewDocumentIntegrationTest(store kv.Store) func(t *testing.T) {
return func(t *testing.T) { return func(t *testing.T) {
ctx := context.Background() ctx := context.Background()
svc := kv.NewService(store) svc := kv.NewService(store)
mockTimeGen := new(mock.TimeGenerator)
if err := svc.Initialize(ctx); err != nil { if err := svc.Initialize(ctx); err != nil {
t.Fatalf("failed to initialize service: %v", err) t.Fatalf("failed to initialize service: %v", err)
} }
svc.TimeGenerator = mockTimeGen
s, err := svc.CreateDocumentStore(ctx, "testing") s, err := svc.CreateDocumentStore(ctx, "testing")
if err != nil { if err != nil {
t.Fatalf("failed to create document store: %v", err) t.Fatalf("failed to create document store: %v", err)
@ -68,6 +73,7 @@ func NewDocumentIntegrationTest(store kv.Store) func(t *testing.T) {
"v1": "v1", "v1": "v1",
}, },
} }
mockTimeGen.FakeValue = time.Date(2009, 1, 2, 3, 0, 0, 0, time.UTC)
if err := s.CreateDocument(ctx, d1, influxdb.AuthorizedWithOrg(s1, o1.Name), influxdb.WithLabel(l1.ID)); err != nil { if err := s.CreateDocument(ctx, d1, influxdb.AuthorizedWithOrg(s1, o1.Name), influxdb.WithLabel(l1.ID)); err != nil {
t.Errorf("failed to create document: %v", err) t.Errorf("failed to create document: %v", err)
} }
@ -82,10 +88,12 @@ func NewDocumentIntegrationTest(store kv.Store) func(t *testing.T) {
"i2": "i2", "i2": "i2",
}, },
} }
mockTimeGen.FakeValue = time.Date(2009, 1, 2, 3, 0, 1, 0, time.UTC)
if err := s.CreateDocument(ctx, d2, influxdb.AuthorizedWithOrg(s2, o1.Name), influxdb.WithLabel(l2.ID)); err == nil { if err := s.CreateDocument(ctx, d2, influxdb.AuthorizedWithOrg(s2, o1.Name), influxdb.WithLabel(l2.ID)); err == nil {
t.Fatalf("should not have be authorized to create document") t.Fatalf("should not have be authorized to create document")
} }
mockTimeGen.FakeValue = time.Date(2009, 1, 2, 3, 0, 1, 0, time.UTC)
if err := s.CreateDocument(ctx, d2, influxdb.AuthorizedWithOrg(s2, o2.Name)); err != nil { if err := s.CreateDocument(ctx, d2, influxdb.AuthorizedWithOrg(s2, o2.Name)); err != nil {
t.Errorf("should have been authorized to create document: %v", err) t.Errorf("should have been authorized to create document: %v", err)
} }
@ -100,6 +108,7 @@ func NewDocumentIntegrationTest(store kv.Store) func(t *testing.T) {
"k2": "v2", "k2": "v2",
}, },
} }
mockTimeGen.FakeValue = time.Date(2009, 1, 2, 3, 0, 2, 0, time.UTC)
if err := s.CreateDocument(ctx, d3, influxdb.AuthorizedWithOrg(s1, o2.Name)); err == nil { if err := s.CreateDocument(ctx, d3, influxdb.AuthorizedWithOrg(s1, o2.Name)); err == nil {
t.Errorf("should not have be authorized to create document") t.Errorf("should not have be authorized to create document")
} }
@ -107,6 +116,7 @@ func NewDocumentIntegrationTest(store kv.Store) func(t *testing.T) {
t.Run("can create unowned document", func(t *testing.T) { t.Run("can create unowned document", func(t *testing.T) {
// TODO(desa): should this be allowed? // TODO(desa): should this be allowed?
mockTimeGen.FakeValue = time.Date(2009, 1, 2, 3, 0, 2, 0, time.UTC)
if err := s.CreateDocument(ctx, d3); err != nil { if err := s.CreateDocument(ctx, d3); err != nil {
t.Fatalf("should have been able to create document: %v", err) t.Fatalf("should have been able to create document: %v", err)
} }
@ -128,14 +138,18 @@ func NewDocumentIntegrationTest(store kv.Store) func(t *testing.T) {
}) })
}) })
d1.Meta.CreatedAt = time.Date(2009, 1, 2, 3, 0, 0, 0, time.UTC)
dl1 := new(influxdb.Document) dl1 := new(influxdb.Document)
*dl1 = *d1 *dl1 = *d1
dl1.Labels = append([]*influxdb.Label{}, l1) dl1.Labels = append([]*influxdb.Label{}, l1)
d2.Meta.CreatedAt = time.Date(2009, 1, 2, 3, 0, 1, 0, time.UTC)
dl2 := new(influxdb.Document) dl2 := new(influxdb.Document)
*dl2 = *d2 *dl2 = *d2
dl2.Labels = append([]*influxdb.Label{}, d2.Labels...) dl2.Labels = append([]*influxdb.Label{}, d2.Labels...)
d3.Meta.CreatedAt = time.Date(2009, 1, 2, 3, 0, 2, 0, time.UTC)
t.Run("bare call to find returns all documents", func(t *testing.T) { t.Run("bare call to find returns all documents", func(t *testing.T) {
ds, err := ss.FindDocuments(ctx) ds, err := ss.FindDocuments(ctx)
if err != nil { if err != nil {
@ -153,8 +167,8 @@ func NewDocumentIntegrationTest(store kv.Store) func(t *testing.T) {
t.Fatalf("failed to retrieve documents: %v", err) t.Fatalf("failed to retrieve documents: %v", err)
} }
if exp, got := []*influxdb.Document{dl1}, ds; !docsEqual(exp, got) { if exp, got := []*influxdb.Document{dl1}, ds; !docsEqual(got, exp) {
t.Errorf("documents are different -got/+want\ndiff %s", docsDiff(exp, got)) t.Errorf("documents are different -got/+want\ndiff %s", docsDiff(got, exp))
} }
}) })