487 lines
15 KiB
Go
487 lines
15 KiB
Go
package bz1_test
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"io/ioutil"
|
|
"math"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/influxdb/influxdb/influxql"
|
|
"github.com/influxdb/influxdb/models"
|
|
"github.com/influxdb/influxdb/tsdb"
|
|
"github.com/influxdb/influxdb/tsdb/engine/bz1"
|
|
)
|
|
|
|
// Ensure the engine can write series metadata and reload it.
|
|
func TestEngine_LoadMetadataIndex_Series(t *testing.T) {
|
|
e := OpenDefaultEngine()
|
|
defer e.Close()
|
|
|
|
// Setup mock that writes the index
|
|
seriesToCreate := []*tsdb.SeriesCreate{
|
|
{Series: tsdb.NewSeries(string(models.MakeKey([]byte("cpu"), map[string]string{"host": "server0"})), map[string]string{"host": "server0"})},
|
|
{Series: tsdb.NewSeries(string(models.MakeKey([]byte("cpu"), map[string]string{"host": "server1"})), map[string]string{"host": "server1"})},
|
|
{Series: tsdb.NewSeries("series with spaces", nil)},
|
|
}
|
|
e.PointsWriter.WritePointsFn = func(a []models.Point) error { return e.WriteIndex(nil, nil, seriesToCreate) }
|
|
|
|
// Write series metadata.
|
|
if err := e.WritePoints(nil, nil, seriesToCreate); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Load metadata index.
|
|
index := tsdb.NewDatabaseIndex()
|
|
if err := e.LoadMetadataIndex(nil, index, make(map[string]*tsdb.MeasurementFields)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Verify index is correct.
|
|
if m := index.Measurement("cpu"); m == nil {
|
|
t.Fatal("measurement not found")
|
|
} else if s := m.SeriesByID(1); s.Key != "cpu,host=server0" || !reflect.DeepEqual(s.Tags, map[string]string{"host": "server0"}) {
|
|
t.Fatalf("unexpected series: %q / %#v", s.Key, s.Tags)
|
|
} else if s = m.SeriesByID(2); s.Key != "cpu,host=server1" || !reflect.DeepEqual(s.Tags, map[string]string{"host": "server1"}) {
|
|
t.Fatalf("unexpected series: %q / %#v", s.Key, s.Tags)
|
|
}
|
|
|
|
if m := index.Measurement("series with spaces"); m == nil {
|
|
t.Fatal("measurement not found")
|
|
} else if s := m.SeriesByID(3); s.Key != "series with spaces" {
|
|
t.Fatalf("unexpected series: %q", s.Key)
|
|
}
|
|
}
|
|
|
|
// Ensure the engine can write field metadata and reload it.
|
|
func TestEngine_LoadMetadataIndex_Fields(t *testing.T) {
|
|
e := OpenDefaultEngine()
|
|
defer e.Close()
|
|
|
|
// Setup mock that writes the index
|
|
fields := map[string]*tsdb.MeasurementFields{
|
|
"cpu": &tsdb.MeasurementFields{
|
|
Fields: map[string]*tsdb.Field{
|
|
"value": &tsdb.Field{ID: 0, Name: "value"},
|
|
},
|
|
},
|
|
}
|
|
e.PointsWriter.WritePointsFn = func(a []models.Point) error { return e.WriteIndex(nil, fields, nil) }
|
|
|
|
// Write series metadata.
|
|
if err := e.WritePoints(nil, fields, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Load metadata index.
|
|
mfs := make(map[string]*tsdb.MeasurementFields)
|
|
if err := e.LoadMetadataIndex(nil, tsdb.NewDatabaseIndex(), mfs); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Verify measurement field is correct.
|
|
if mf := mfs["cpu"]; mf == nil {
|
|
t.Fatal("measurement fields not found")
|
|
} else if !reflect.DeepEqual(mf.Fields, map[string]*tsdb.Field{"value": &tsdb.Field{ID: 0, Name: "value"}}) {
|
|
t.Fatalf("unexpected fields: %#v", mf.Fields)
|
|
}
|
|
}
|
|
|
|
// Ensure the engine can write points to storage.
|
|
func TestEngine_WritePoints_PointsWriter(t *testing.T) {
|
|
e := OpenDefaultEngine()
|
|
defer e.Close()
|
|
|
|
// Points to be inserted.
|
|
points := []models.Point{
|
|
models.MustNewPoint("cpu", models.Tags{}, models.Fields{"foo": "bar"}, time.Unix(0, 1)),
|
|
models.MustNewPoint("cpu", models.Tags{}, models.Fields{"foo": "bar"}, time.Unix(0, 0)),
|
|
models.MustNewPoint("cpu", models.Tags{}, models.Fields{"foo": "bar"}, time.Unix(1, 0)),
|
|
|
|
models.MustNewPoint("cpu", models.Tags{"host": "serverA"}, models.Fields{"foo": "bar"}, time.Unix(0, 0)),
|
|
}
|
|
|
|
// Mock points writer to ensure points are passed through.
|
|
var invoked bool
|
|
e.PointsWriter.WritePointsFn = func(a []models.Point) error {
|
|
invoked = true
|
|
if !reflect.DeepEqual(points, a) {
|
|
t.Fatalf("unexpected points: %#v", a)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Write points against two separate series.
|
|
if err := e.WritePoints(points, nil, nil); err != nil {
|
|
t.Fatal(err)
|
|
} else if !invoked {
|
|
t.Fatal("PointsWriter.WritePoints() not called")
|
|
}
|
|
}
|
|
|
|
// Ensure the engine can return errors from the points writer.
|
|
func TestEngine_WritePoints_ErrPointsWriter(t *testing.T) {
|
|
e := OpenDefaultEngine()
|
|
defer e.Close()
|
|
|
|
// Ensure points writer returns an error.
|
|
e.PointsWriter.WritePointsFn = func(a []models.Point) error { return errors.New("marker") }
|
|
|
|
// Write to engine.
|
|
if err := e.WritePoints(nil, nil, nil); err == nil || err.Error() != `write points: marker` {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Ensure the engine can write points to the index.
|
|
func TestEngine_WriteIndex_Append(t *testing.T) {
|
|
e := OpenDefaultEngine()
|
|
defer e.Close()
|
|
|
|
// Create codec.
|
|
codec := tsdb.NewFieldCodec(map[string]*tsdb.Field{
|
|
"value": {ID: uint8(1), Name: "value", Type: influxql.Float},
|
|
})
|
|
|
|
// Append points to index.
|
|
if err := e.WriteIndex(map[string][][]byte{
|
|
"cpu": [][]byte{
|
|
append(u64tob(1), MustEncodeFields(codec, models.Fields{"value": float64(10)})...),
|
|
append(u64tob(2), MustEncodeFields(codec, models.Fields{"value": float64(20)})...),
|
|
},
|
|
"mem": [][]byte{
|
|
append(u64tob(0), MustEncodeFields(codec, models.Fields{"value": float64(30)})...),
|
|
},
|
|
}, nil, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Start transaction.
|
|
tx := e.MustBegin(false)
|
|
defer tx.Rollback()
|
|
|
|
// Iterate over "cpu" series.
|
|
c := tx.Cursor("cpu", []string{"value"}, codec, true)
|
|
if k, v := c.SeekTo(0); k != 1 || v.(float64) != float64(10) {
|
|
t.Fatalf("unexpected key/value: %x / %x", k, v)
|
|
} else if k, v = c.Next(); k != 2 || v.(float64) != float64(20) {
|
|
t.Fatalf("unexpected key/value: %x / %x", k, v)
|
|
} else if k, _ = c.Next(); k != tsdb.EOF {
|
|
t.Fatalf("unexpected key/value: %x / %x", k, v)
|
|
}
|
|
|
|
// Iterate over "mem" series.
|
|
c = tx.Cursor("mem", []string{"value"}, codec, true)
|
|
if k, v := c.SeekTo(0); k != 0 || v.(float64) != float64(30) {
|
|
t.Fatalf("unexpected key/value: %x / %x", k, v)
|
|
} else if k, _ = c.Next(); k != tsdb.EOF {
|
|
t.Fatalf("unexpected key/value: %x / %x", k, v)
|
|
}
|
|
}
|
|
|
|
// Ensure the engine can rewrite blocks that contain the new point range.
|
|
func TestEngine_WriteIndex_Insert(t *testing.T) {
|
|
e := OpenDefaultEngine()
|
|
defer e.Close()
|
|
|
|
// Create codec.
|
|
codec := tsdb.NewFieldCodec(map[string]*tsdb.Field{
|
|
"value": {ID: uint8(1), Name: "value", Type: influxql.Float},
|
|
})
|
|
|
|
// Write initial points to index.
|
|
if err := e.WriteIndex(map[string][][]byte{
|
|
"cpu": [][]byte{
|
|
append(u64tob(10), MustEncodeFields(codec, models.Fields{"value": float64(10)})...),
|
|
append(u64tob(20), MustEncodeFields(codec, models.Fields{"value": float64(20)})...),
|
|
append(u64tob(30), MustEncodeFields(codec, models.Fields{"value": float64(30)})...),
|
|
},
|
|
}, nil, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Write overlapping points to index.
|
|
if err := e.WriteIndex(map[string][][]byte{
|
|
"cpu": [][]byte{
|
|
append(u64tob(9), MustEncodeFields(codec, models.Fields{"value": float64(9)})...),
|
|
append(u64tob(10), MustEncodeFields(codec, models.Fields{"value": float64(255)})...),
|
|
append(u64tob(25), MustEncodeFields(codec, models.Fields{"value": float64(25)})...),
|
|
append(u64tob(31), MustEncodeFields(codec, models.Fields{"value": float64(31)})...),
|
|
},
|
|
}, nil, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Write overlapping points to index again.
|
|
if err := e.WriteIndex(map[string][][]byte{
|
|
"cpu": [][]byte{
|
|
append(u64tob(31), MustEncodeFields(codec, models.Fields{"value": float64(255)})...),
|
|
},
|
|
}, nil, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Start transaction.
|
|
tx := e.MustBegin(false)
|
|
defer tx.Rollback()
|
|
|
|
// Iterate over "cpu" series.
|
|
c := tx.Cursor("cpu", []string{"value"}, codec, true)
|
|
if k, v := c.SeekTo(0); k != 9 || v.(float64) != float64(9) {
|
|
t.Fatalf("unexpected key/value: %x / %x", k, v)
|
|
} else if k, v = c.Next(); k != 10 || v.(float64) != float64(255) {
|
|
t.Fatalf("unexpected key/value: %x / %x", k, v)
|
|
} else if k, v = c.Next(); k != 20 || v.(float64) != float64(20) {
|
|
t.Fatalf("unexpected key/value: %x / %x", k, v)
|
|
} else if k, v = c.Next(); k != 25 || v.(float64) != float64(25) {
|
|
t.Fatalf("unexpected key/value: %x / %x", k, v)
|
|
} else if k, v = c.Next(); k != 30 || v.(float64) != float64(30) {
|
|
t.Fatalf("unexpected key/value: %x / %x", k, v)
|
|
} else if k, v = c.Next(); k != 31 || v.(float64) != float64(255) {
|
|
t.Fatalf("unexpected key/value: %x / %x", k, v)
|
|
}
|
|
}
|
|
|
|
// Ensure the engine can rewrite blocks that contain the new point range.
|
|
func TestEngine_Cursor_Reverse(t *testing.T) {
|
|
e := OpenDefaultEngine()
|
|
defer e.Close()
|
|
|
|
// Create codec.
|
|
codec := tsdb.NewFieldCodec(map[string]*tsdb.Field{
|
|
"value": {ID: uint8(1), Name: "value", Type: influxql.Float},
|
|
})
|
|
|
|
// Write initial points to index.
|
|
if err := e.WriteIndex(map[string][][]byte{
|
|
"cpu": [][]byte{
|
|
append(u64tob(10), MustEncodeFields(codec, models.Fields{"value": float64(10)})...),
|
|
append(u64tob(20), MustEncodeFields(codec, models.Fields{"value": float64(20)})...),
|
|
append(u64tob(30), MustEncodeFields(codec, models.Fields{"value": float64(30)})...),
|
|
},
|
|
}, nil, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Write overlapping points to index.
|
|
if err := e.WriteIndex(map[string][][]byte{
|
|
"cpu": [][]byte{
|
|
append(u64tob(9), MustEncodeFields(codec, models.Fields{"value": float64(9)})...),
|
|
append(u64tob(10), MustEncodeFields(codec, models.Fields{"value": float64(255)})...),
|
|
append(u64tob(25), MustEncodeFields(codec, models.Fields{"value": float64(25)})...),
|
|
append(u64tob(31), MustEncodeFields(codec, models.Fields{"value": float64(31)})...),
|
|
},
|
|
}, nil, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Write overlapping points to index again.
|
|
if err := e.WriteIndex(map[string][][]byte{
|
|
"cpu": [][]byte{
|
|
append(u64tob(31), MustEncodeFields(codec, models.Fields{"value": float64(255)})...),
|
|
},
|
|
}, nil, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Start transaction.
|
|
tx := e.MustBegin(false)
|
|
defer tx.Rollback()
|
|
|
|
// Iterate over "cpu" series.
|
|
c := tx.Cursor("cpu", []string{"value"}, codec, false)
|
|
if k, v := c.SeekTo(math.MaxInt64); k != 31 || v.(float64) != float64(255) {
|
|
t.Fatalf("unexpected key/value: %x / %x", k, v)
|
|
} else if k, v = c.Next(); k != 30 || v.(float64) != float64(30) {
|
|
t.Fatalf("unexpected key/value: %x / %x", k, v)
|
|
} else if k, v = c.Next(); k != 25 || v.(float64) != float64(25) {
|
|
t.Fatalf("unexpected key/value: %x / %x", k, v)
|
|
} else if k, v = c.Next(); k != 20 || v.(float64) != float64(20) {
|
|
t.Fatalf("unexpected key/value: %x / %x", k, v)
|
|
} else if k, v = c.Next(); k != 10 || v.(float64) != float64(255) {
|
|
t.Fatalf("unexpected key/value: %x / %x", k, v)
|
|
} else if k, v = c.SeekTo(0); k != 9 || v.(float64) != float64(9) {
|
|
t.Fatalf("unexpected key/value: %x / %x", k, v)
|
|
}
|
|
}
|
|
|
|
// Ensure that the engine properly seeks to a block when the seek value is in the middle.
|
|
func TestEngine_WriteIndex_SeekAgainstInBlockValue(t *testing.T) {
|
|
e := OpenDefaultEngine()
|
|
defer e.Close()
|
|
|
|
// Create codec.
|
|
codec := tsdb.NewFieldCodec(map[string]*tsdb.Field{
|
|
"value": {ID: uint8(1), Name: "value", Type: influxql.String},
|
|
})
|
|
|
|
// make sure we have data split across two blocks
|
|
dataSize := (bz1.DefaultBlockSize - 16) / 2
|
|
data := strings.Repeat("*", dataSize)
|
|
|
|
// Write initial points to index.
|
|
if err := e.WriteIndex(map[string][][]byte{
|
|
"cpu": [][]byte{
|
|
append(u64tob(10), MustEncodeFields(codec, models.Fields{"value": data})...),
|
|
append(u64tob(20), MustEncodeFields(codec, models.Fields{"value": data})...),
|
|
append(u64tob(30), MustEncodeFields(codec, models.Fields{"value": data})...),
|
|
append(u64tob(40), MustEncodeFields(codec, models.Fields{"value": data})...),
|
|
},
|
|
}, nil, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Start transaction.
|
|
tx := e.MustBegin(false)
|
|
defer tx.Rollback()
|
|
|
|
// Ensure that we can seek to a block in the middle
|
|
c := tx.Cursor("cpu", []string{"value"}, codec, true)
|
|
if k, _ := c.SeekTo(15); k != 20 {
|
|
t.Fatalf("expected to seek to time 20, but got %d", k)
|
|
}
|
|
// Ensure that we can seek to the block on the end
|
|
if k, _ := c.SeekTo(35); k != 40 {
|
|
t.Fatalf("expected to seek to time 40, but got %d", k)
|
|
}
|
|
}
|
|
|
|
// Ensure the engine ignores writes without keys.
|
|
func TestEngine_WriteIndex_NoKeys(t *testing.T) {
|
|
e := OpenDefaultEngine()
|
|
defer e.Close()
|
|
if err := e.WriteIndex(nil, nil, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Ensure the engine ignores writes without points in a key.
|
|
func TestEngine_WriteIndex_NoPoints(t *testing.T) {
|
|
e := OpenDefaultEngine()
|
|
defer e.Close()
|
|
if err := e.WriteIndex(map[string][][]byte{"cpu": nil}, nil, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Engine represents a test wrapper for bz1.Engine.
|
|
type Engine struct {
|
|
*bz1.Engine
|
|
PointsWriter EnginePointsWriter
|
|
}
|
|
|
|
// NewEngine returns a new instance of Engine.
|
|
func NewEngine(opt tsdb.EngineOptions) *Engine {
|
|
// Generate temporary file.
|
|
f, _ := ioutil.TempFile("", "bz1-")
|
|
f.Close()
|
|
os.Remove(f.Name())
|
|
walPath := filepath.Join(f.Name(), "wal")
|
|
|
|
// Create test wrapper and attach mocks.
|
|
e := &Engine{
|
|
Engine: bz1.NewEngine(f.Name(), walPath, opt).(*bz1.Engine),
|
|
}
|
|
e.Engine.WAL = &e.PointsWriter
|
|
return e
|
|
}
|
|
|
|
// OpenEngine returns an opened instance of Engine. Panic on error.
|
|
func OpenEngine(opt tsdb.EngineOptions) *Engine {
|
|
e := NewEngine(opt)
|
|
if err := e.Open(); err != nil {
|
|
panic(err)
|
|
}
|
|
return e
|
|
}
|
|
|
|
// OpenDefaultEngine returns an open Engine with default options.
|
|
func OpenDefaultEngine() *Engine { return OpenEngine(tsdb.NewEngineOptions()) }
|
|
|
|
// Close closes the engine and removes all data.
|
|
func (e *Engine) Close() error {
|
|
e.Engine.Close()
|
|
os.RemoveAll(e.Path())
|
|
return nil
|
|
}
|
|
|
|
// MustBegin returns a new tranaction. Panic on error.
|
|
func (e *Engine) MustBegin(writable bool) tsdb.Tx {
|
|
tx, err := e.Begin(writable)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return tx
|
|
}
|
|
|
|
// EnginePointsWriter represents a mock that implements Engine.PointsWriter.
|
|
type EnginePointsWriter struct {
|
|
WritePointsFn func(points []models.Point) error
|
|
}
|
|
|
|
func (w *EnginePointsWriter) WritePoints(points []models.Point, measurementFieldsToSave map[string]*tsdb.MeasurementFields, seriesToCreate []*tsdb.SeriesCreate) error {
|
|
return w.WritePointsFn(points)
|
|
}
|
|
|
|
func (w *EnginePointsWriter) LoadMetadataIndex(index *tsdb.DatabaseIndex, measurementFields map[string]*tsdb.MeasurementFields) error {
|
|
return nil
|
|
}
|
|
|
|
func (w *EnginePointsWriter) DeleteSeries(keys []string) error { return nil }
|
|
|
|
func (w *EnginePointsWriter) Open() error { return nil }
|
|
|
|
func (w *EnginePointsWriter) Close() error { return nil }
|
|
|
|
func (w *EnginePointsWriter) Cursor(series string, fields []string, dec *tsdb.FieldCodec, ascending bool) tsdb.Cursor {
|
|
return &Cursor{ascending: ascending}
|
|
}
|
|
|
|
func (w *EnginePointsWriter) Flush() error { return nil }
|
|
|
|
// Cursor represents a mock that implements tsdb.Curosr.
|
|
type Cursor struct {
|
|
ascending bool
|
|
}
|
|
|
|
func (c *Cursor) Ascending() bool { return c.ascending }
|
|
|
|
func (c *Cursor) SeekTo(key int64) (int64, interface{}) { return tsdb.EOF, nil }
|
|
|
|
func (c *Cursor) Next() (int64, interface{}) { return tsdb.EOF, nil }
|
|
|
|
// MustEncodeFields encodes fields with codec. Panic on error.
|
|
func MustEncodeFields(codec *tsdb.FieldCodec, fields models.Fields) []byte {
|
|
b, err := codec.EncodeFields(fields)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// copyBytes returns a copy of a byte slice.
|
|
func copyBytes(b []byte) []byte {
|
|
if b == nil {
|
|
return nil
|
|
}
|
|
|
|
other := make([]byte, len(b))
|
|
copy(other, b)
|
|
return other
|
|
}
|
|
|
|
// u64tob converts a uint64 into an 8-byte slice.
|
|
func u64tob(v uint64) []byte {
|
|
b := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(b, v)
|
|
return b
|
|
}
|
|
|
|
// btou64 converts an 8-byte slice into an uint64.
|
|
func btou64(b []byte) uint64 { return binary.BigEndian.Uint64(b) }
|