package tests import ( "encoding/json" "fmt" "math/rand" "net/url" "os" "reflect" "sort" "strings" "sync" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/influxdata/influxdb/models" ) var db = "db0" var rp = "rp0" // Makes it easy to debug times by emitting rfc formatted strings instead // of numbers. var tme = func(t int64) string { // return time.Unix(0, t).UTC().String() + fmt.Sprintf("(%d)", t) return fmt.Sprint(t) // Just emit the number } type Command func() (string, error) func setupCommands(s *LocalServer, tracker *SeriesTracker) []Command { var measurementN = 10 var seriesN = 100 var commands []Command r := rand.New(rand.NewSource(seed)) // Command for inserting some series data. commands = append(commands, func() (string, error) { name := fmt.Sprintf("m%d", r.Intn(measurementN)) tags := models.NewTags(map[string]string{fmt.Sprintf("s%d", r.Intn(seriesN)): "a"}) tracker.Lock() pt := tracker.AddSeries(name, tags) _, err := s.Write(db, rp, pt, nil) if err != nil { return "", err } defer tracker.Unlock() return fmt.Sprintf("INSERT %s", pt), tracker.Verify() }) // Command for dropping an entire measurement. commands = append(commands, func() (string, error) { name := fmt.Sprintf("m%d", r.Intn(measurementN)) tracker.Lock() tracker.DeleteMeasurement(name) query := fmt.Sprintf("DROP MEASUREMENT %s", name) _, err := s.QueryWithParams(query, url.Values{"db": []string{"db0"}}) if err != nil { return "", err } defer tracker.Unlock() return query, tracker.Verify() }) // Command for dropping a single series. commands = append(commands, func() (string, error) { name := fmt.Sprintf("m%d", r.Intn(measurementN)) tagKey := fmt.Sprintf("s%d", r.Intn(seriesN)) tags := models.NewTags(map[string]string{tagKey: "a"}) tracker.Lock() tracker.DropSeries(name, tags) query := fmt.Sprintf("DROP SERIES FROM %q WHERE %q = 'a'", name, tagKey) _, err := s.QueryWithParams(query, url.Values{"db": []string{"db0"}}) if err != nil { return "", err } defer tracker.Unlock() return query, tracker.Verify() }) // Command for dropping a single series. commands = append(commands, func() (string, error) { name := fmt.Sprintf("m%d", r.Intn(measurementN)) tracker.Lock() min, max := tracker.DeleteRandomRange(name) query := fmt.Sprintf("DELETE FROM %q WHERE time >= %d AND time <= %d ", name, min, max) _, err := s.QueryWithParams(query, url.Values{"db": []string{"db0"}}) if err != nil { return "", err } defer tracker.Unlock() query = fmt.Sprintf("DELETE FROM %q WHERE time >= %s AND time <= %s ", name, tme(min), tme(max)) return query, tracker.Verify() }) return commands } // TestServer_Delete_Series sets up a concurrent collection of clients that continuously // write data and delete series, measurements and shards. // // The purpose of this test is to provide a randomised, highly concurrent test // to shake out bugs involving the interactions between shards, their indexes, // and a database's series file. func TestServer_DELETE_DROP_SERIES_DROP_MEASUREMENT(t *testing.T) { t.Parallel() if testing.Short() || os.Getenv("GORACE") != "" { t.Skip("Skipping test in short or race mode.") } N := 5000 shardN := 10 r := rand.New(rand.NewSource(seed)) t.Logf("***** Seed set to %d *****\n", seed) if testing.Short() { t.Skip("Skipping in short mode") } s := OpenDefaultServer(NewConfig()) defer s.Close() localServer := s.(*LocalServer) // First initialize some writes such that we end up with 10 shards. // The first point refers to 1970-01-01 00:00:00.01 +0000 UTC and each subsequent // point is 7 days into the future. queries := make([]string, 0, N) data := make([]string, 0, shardN) for i := int64(0); i < int64(cap(data)); i++ { query := fmt.Sprintf(`a val=1 %d`, 10000000+(int64(time.Hour)*24*7*i)) queries = append(queries, fmt.Sprintf("INSERT %s", query)) data = append(data, query) } if _, err := s.Write(db, rp, strings.Join(data, "\n"), nil); err != nil { t.Fatal(err) } tracker := NewSeriesTracker(r, localServer, db, rp) commands := setupCommands(localServer, tracker) for i := 0; i < N; i++ { query, err := commands[r.Intn(len(commands))]() queries = append(queries, query) if err != nil { emit := queries if len(queries) > 1000 { emit = queries[len(queries)-1000:] } t.Logf("Emitting last 1000 queries of %d total:\n%s\n", len(queries), strings.Join(emit, "\n")) t.Logf("Current points in Series Tracker index:\n%s\n", tracker.DumpPoints()) t.Fatal(err) } } } // **** The following tests are specific examples discovered using **** // TestServer_DELETE_DROP_SERIES_DROP_MEASUREMENT. They're added to prevent // regressions. // This test never explicitly failed, but it's a special case of // TestServer_Insert_Delete_1515688266259660938. func TestServer_Insert_Delete_1515688266259660938_same_shard(t *testing.T) { // Original seed was 1515688266259660938. t.Parallel() if testing.Short() || os.Getenv("GORACE") != "" { t.Skip("Skipping test in short or race mode.") } s := OpenDefaultServer(NewConfig()) defer s.Close() for i := 0; i < 100; i++ { mustWrite(s, "m4,s67=a v=1 4", "m4,s67=a v=1 12", "m4,s1=a v=1 15") mustDelete(s, "m4", 1, 10) // Compare series left in index. gotSeries := mustGetSeries(s) expectedSeries := []string{"m4,s1=a", "m4,s67=a"} if !reflect.DeepEqual(gotSeries, expectedSeries) { t.Fatalf("got series %v, expected %v", gotSeries, expectedSeries) } mustDropCreate(s) } } // This test failed with seed 1515688266259660938. func TestServer_Insert_Delete_1515688266259660938(t *testing.T) { // Original seed was 1515688266259660938. t.Parallel() if testing.Short() || os.Getenv("GORACE") != "" { t.Skip("Skipping test in short or race mode.") } s := OpenDefaultServer(NewConfig()) defer s.Close() for i := 0; i < 100; i++ { mustWrite(s, "m4,s67=a v=1 2127318532111304", // should be deleted "m4,s67=a v=1 4840422259072956", "m4,s1=a v=1 4777375719836601") mustDelete(s, "m4", 1134567692141289, 2233755799041351) // Compare series left in index. gotSeries := mustGetSeries(s) expectedSeries := []string{"m4,s1=a", "m4,s67=a"} if !reflect.DeepEqual(gotSeries, expectedSeries) { t.Fatalf("got series %v, expected %v", gotSeries, expectedSeries) } mustDropCreate(s) } } // This test failed with seed 1515771752164780713. func TestServer_Insert_Delete_1515771752164780713(t *testing.T) { // Original seed was 1515771752164780713. t.Parallel() if testing.Short() || os.Getenv("GORACE") != "" { t.Skip("Skipping test in short or race mode.") } s := OpenDefaultServer(NewConfig()) defer s.Close() mustWrite(s, "m6,s72=a v=1 641480139110750") // series id 257 in shard 1 mustWrite(s, "m6,s32=a v=1 1320128148356150") // series id 259 in shard 2 mustDelete(s, "m6", 1316178840387070, 3095172845699329) // deletes m6,s32=a (259) - shard 2 now empty mustWrite(s, "m6,s61=a v=1 47161015166211") // series id 261 in shard 3 mustWrite(s, "m6,s67=a v=1 4466443248294177") // series 515 in shard 4 mustDelete(s, "m6", 495574950798826, 2963503790804876) // deletes m6,s72 (257) - shard 1 now empty // Compare series left in index. gotSeries := mustGetSeries(s) expectedSeries := []string{"m6,s61=a", "m6,s67=a"} if !reflect.DeepEqual(gotSeries, expectedSeries) { t.Fatalf("got series %v, expected %v", gotSeries, expectedSeries) } } // This test failed with seed 1515777603585914810. func TestServer_Insert_Delete_1515777603585914810(t *testing.T) { // Original seed was 1515777603585914810. t.Parallel() s := OpenDefaultServer(NewConfig()) defer s.Close() mustWrite(s, "m5,s99=a v=1 1") mustDelete(s, "m5", 0, 1) mustWrite(s, "m5,s99=a v=1 1") gotSeries := mustGetSeries(s) expectedSeries := []string{"m5,s99=a"} if !reflect.DeepEqual(gotSeries, expectedSeries) { t.Fatalf("got series %v, expected %v", gotSeries, expectedSeries) } } // This test reproduces the issue identified in https://github.com/influxdata/influxdb/issues/10052 func TestServer_Insert_Delete_10052(t *testing.T) { t.Parallel() s := OpenDefaultServer(NewConfig()) defer s.Close() mustWrite(s, "ping,server=ping a=1,b=2,c=3,d=4,e=5 1", "ping,server=ping a=1,b=2,c=3,d=4,e=5 2", "ping,server=ping a=1,b=2,c=3,d=4,e=5 3", "ping,server=ping a=1,b=2,c=3,d=4,e=5 4", "ping,server=ping a=1,b=2,c=3,d=4,e=5 5", "ping,server=ping a=1,b=2,c=3,d=4,e=5 6", ) mustDropMeasurement(s, "ping") gotSeries := mustGetSeries(s) expectedSeries := []string(nil) if !reflect.DeepEqual(gotSeries, expectedSeries) { t.Fatalf("got series %v, expected %v", gotSeries, expectedSeries) } mustWrite(s, "ping v=1 1") gotSeries = mustGetSeries(s) expectedSeries = []string{"ping"} if !reflect.DeepEqual(gotSeries, expectedSeries) { t.Fatalf("got series %v, expected %v", gotSeries, expectedSeries) } gotSeries = mustGetFieldKeys(s) expectedSeries = []string{"v"} if !reflect.DeepEqual(gotSeries, expectedSeries) { t.Fatalf("got series %v, expected %v", gotSeries, expectedSeries) } } func mustGetSeries(s Server) []string { // Compare series left in index. result, err := s.QueryWithParams("SHOW SERIES", url.Values{"db": []string{"db0"}}) if err != nil { panic(err) } gotSeries, err := valuesFromShowQuery(result) if err != nil { panic(err) } return gotSeries } func mustGetFieldKeys(s Server) []string { // Compare series left in index. result, err := s.QueryWithParams("SHOW FIELD KEYS", url.Values{"db": []string{"db0"}}) if err != nil { panic(err) } gotSeries, err := valuesFromShowQuery(result) if err != nil { panic(err) } return gotSeries } func mustDropCreate(s Server) { if err := s.DropDatabase(db); err != nil { panic(err) } if err := s.CreateDatabaseAndRetentionPolicy(db, NewRetentionPolicySpec(rp, 1, 0), true); err != nil { panic(err) } } func mustWrite(s Server, points ...string) { if _, err := s.Write(db, rp, strings.Join(points, "\n"), nil); err != nil { panic(err) } } func mustDelete(s Server, name string, min, max int64) { query := fmt.Sprintf("DELETE FROM %q WHERE time >= %d AND time <= %d ", name, min, max) if _, err := s.QueryWithParams(query, url.Values{"db": []string{db}}); err != nil { panic(err) } } func mustDropMeasurement(s Server, name string) { query := fmt.Sprintf("DROP MEASUREMENT %q", name) if _, err := s.QueryWithParams(query, url.Values{"db": []string{db}}); err != nil { panic(err) } } // SeriesTracker is a lockable tracker of which shards should own which series. type SeriesTracker struct { sync.RWMutex r *rand.Rand // The testing server server *LocalServer // series maps a series key to a value that determines which shards own the // series. series map[string]uint64 // seriesPoints maps a series key to all the times that the series is written. seriesPoints map[string][]int64 // measurements maps a measurement name to a value that determines which // shards own the measurement. measurements map[string]uint64 // measurementsSeries maps which series keys belong to which measurement. measurementsSeries map[string]map[string]struct{} // shardTimeRanges maintains the time ranges that a shard spans shardTimeRanges map[uint64][2]int64 shardIDs []uint64 } func NewSeriesTracker(r *rand.Rand, server *LocalServer, db, rp string) *SeriesTracker { tracker := &SeriesTracker{ r: r, series: make(map[string]uint64), seriesPoints: make(map[string][]int64), measurements: make(map[string]uint64), measurementsSeries: make(map[string]map[string]struct{}), shardTimeRanges: make(map[uint64][2]int64), server: server, } data := server.MetaClient.Data() sgs, err := data.ShardGroups(db, rp) if err != nil { panic(err) } for _, sg := range sgs { tracker.shardTimeRanges[sg.ID] = [2]int64{sg.StartTime.UnixNano(), sg.EndTime.UnixNano()} tracker.shardIDs = append(tracker.shardIDs, sg.ID) } // Add initial series for i, sid := range tracker.shardIDs { // Map the shard to the series. tracker.series["a"] = tracker.series["a"] | (1 << sid) // Map the shard to the measurement. tracker.measurements["a"] = tracker.measurements["a"] | (1 << sid) // Map the timstamp of the point in this shard to the series. tracker.seriesPoints["a"] = append(tracker.seriesPoints["a"], 10000000+(int64(time.Hour)*24*7*int64(i))) } // Map initial series to measurement. tracker.measurementsSeries["a"] = map[string]struct{}{"a": struct{}{}} return tracker } // AddSeries writes a point for the provided series to the provided shard. The // exact time of the point is randomised within all the shards' boundaries. // The point string is returned, to be inserted into the server. func (s *SeriesTracker) AddSeries(name string, tags models.Tags) string { // generate a random shard and time within it. time, shard := s.randomTime() pt, err := models.NewPoint(name, tags, models.Fields{"v": 1.0}, time) if err != nil { panic(err) } key := string(pt.Key()) s.series[key] = s.series[key] | (1 << shard) s.seriesPoints[key] = append(s.seriesPoints[key], time.UnixNano()) // Update measurement map s.measurements[name] = s.measurements[name] | (1 << shard) // Update measurement -> series mapping if _, ok := s.measurementsSeries[name]; !ok { s.measurementsSeries[name] = map[string]struct{}{string(key): struct{}{}} } else { s.measurementsSeries[name][string(key)] = struct{}{} } return pt.String() } // DeleteMeasurement deletes all series associated with the provided measurement // from the tracker. func (s *SeriesTracker) DeleteMeasurement(name string) { // Delete from shard -> measurement mapping delete(s.measurements, name) // Get any series associated with measurement, and delete them. series := s.measurementsSeries[name] // Remove all series associated with this measurement. for key := range series { delete(s.series, key) delete(s.seriesPoints, key) } delete(s.measurementsSeries, name) } // DropSeries deletes a specific series, removing any measurements if no series // are owned by them any longer. func (s *SeriesTracker) DropSeries(name string, tags models.Tags) { pt, err := models.NewPoint(name, tags, models.Fields{"v": 1.0}, time.Now()) if err != nil { panic(err) } key := string(pt.Key()) _, ok := s.series[key] if ok { s.cleanupSeries(name, key) // Remove all series data. } } // cleanupSeries removes any traces of series that no longer have any point // data. func (s *SeriesTracker) cleanupSeries(name, key string) { // Remove series references delete(s.series, key) delete(s.measurementsSeries[name], key) delete(s.seriesPoints, key) // Check if that was the last series for a measurement if len(s.measurementsSeries[name]) == 0 { delete(s.measurementsSeries, name) // Remove the measurement delete(s.measurements, name) } } // DeleteRandomRange deletes all series data within a random time range for the // provided measurement. func (s *SeriesTracker) DeleteRandomRange(name string) (int64, int64) { t1, _ := s.randomTime() t2, _ := s.randomTime() min, max := t1.UnixNano(), t2.UnixNano() if t2.Before(t1) { min, max = t2.UnixNano(), t1.UnixNano() } if min > max { panic(fmt.Sprintf("min time %d > max %d", min, max)) } // Get all the series associated with this measurement. series := s.measurementsSeries[name] if len(series) == 0 { // Nothing to do return min, max } // For each series, check for, and remove, any points that fall within the // time range. var seriesToDelete []string for serie := range series { points := s.seriesPoints[serie] sort.Sort(sortedInt64(points)) // Find min and max index that fall in range. var minIdx, maxIdx = -1, -1 for i, p := range points { if minIdx == -1 && p >= min { minIdx = i } if p <= max { maxIdx = i } } // If either the minIdx or maxIdx are not set, then none of the points // fall in that boundary of the domain. if minIdx == -1 || maxIdx == -1 { continue } // Cut those points from the series points slice. s.seriesPoints[serie] = append(points[:minIdx], points[maxIdx+1:]...) // Was that the last point for the series? if len(s.seriesPoints[serie]) == 0 { seriesToDelete = append(seriesToDelete, serie) } } // Cleanup any removed series/measurements. for _, key := range seriesToDelete { s.cleanupSeries(name, key) } return min, max } // randomTime generates a random time to insert a point, such that it will fall // within one of the shards' time boundaries. func (s *SeriesTracker) randomTime() (time.Time, uint64) { // Pick a random shard id := s.shardIDs[s.r.Intn(len(s.shardIDs))] // Get min and max time range of the shard. min, max := s.shardTimeRanges[id][0], s.shardTimeRanges[id][1] if min >= max { panic(fmt.Sprintf("min %d >= max %d", min, max)) } tme := s.r.Int63n(max-min) + min // Will result in a range [min, max) if tme < min || tme >= max { panic(fmt.Sprintf("generated time %d is out of bounds [%d, %d)", tme, min, max)) } return time.Unix(0, tme), id } // Verify verifies that the server's view of the index/series file matches the // series tracker's. func (s *SeriesTracker) Verify() error { res, err := s.server.QueryWithParams("SHOW SERIES", url.Values{"db": []string{"db0"}}) if err != nil { return err } // Get all series... gotSeries, err := valuesFromShowQuery(res) if err != nil { return err } expectedSeries := make([]string, 0, len(s.series)) for series := range s.series { expectedSeries = append(expectedSeries, series) } sort.Strings(expectedSeries) if !reflect.DeepEqual(gotSeries, expectedSeries) { return fmt.Errorf("verification failed:\ngot series: %v\nexpected series: %v\ndifference: %s", gotSeries, expectedSeries, cmp.Diff(gotSeries, expectedSeries)) } return nil } // valuesFromShowQuery extracts a lexicographically sorted set of series keys // from a SHOW SERIES query. func valuesFromShowQuery(result string) ([]string, error) { // Get all series... var results struct { Results []struct { Series []struct { Values [][]string `json:"values"` } `json:"series"` } `json:"results"` } if err := json.Unmarshal([]byte(result), &results); err != nil { return nil, err } var gotSeries []string for _, ser := range results.Results { for _, values := range ser.Series { for _, v := range values.Values { gotSeries = append(gotSeries, v[0]) } } } // series are returned sorted by name then tag key then tag value, which is // not the same as lexicographic order. sort.Strings(gotSeries) return gotSeries, nil } // DumpPoints returns all the series points. func (s *SeriesTracker) DumpPoints() string { keys := make([]string, 0, len(s.seriesPoints)) for key := range s.seriesPoints { keys = append(keys, key) } sort.Strings(keys) for i, key := range keys { // We skip key "a" as it's just there to initialise the shards. if key == "a" { continue } points := s.seriesPoints[key] sort.Sort(sortedInt64(points)) pointStr := make([]string, 0, len(points)) for _, p := range points { pointStr = append(pointStr, tme(p)) } keys[i] = fmt.Sprintf("%s: %v", key, pointStr) } return strings.Join(keys, "\n") } type sortedInt64 []int64 func (a sortedInt64) Len() int { return len(a) } func (a sortedInt64) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a sortedInt64) Less(i, j int) bool { return a[i] < a[j] }