2019-07-12 18:35:20 +01:00
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
2019-08-26 11:19:02 +01:00
package queue_test
2019-07-12 18:35:20 +01:00
import (
2020-02-21 21:32:05 +00:00
"math/rand"
2019-07-12 18:35:20 +01:00
"testing"
2019-07-22 15:10:42 +01:00
"time"
2019-07-12 18:35:20 +01:00
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
2020-12-02 15:45:33 +00:00
"go.uber.org/zap/zaptest"
2019-07-12 18:35:20 +01:00
2019-12-27 11:48:47 +00:00
"storj.io/common/testcontext"
2021-06-17 16:05:04 +01:00
"storj.io/common/testrand"
"storj.io/common/uuid"
2021-04-23 10:52:40 +01:00
"storj.io/private/dbutil/pgtest"
"storj.io/private/dbutil/tempdb"
2019-07-12 18:35:20 +01:00
"storj.io/storj/satellite"
2021-06-17 16:05:04 +01:00
"storj.io/storj/satellite/metabase"
"storj.io/storj/satellite/repair/queue"
2020-12-02 15:45:33 +00:00
"storj.io/storj/satellite/satellitedb"
2019-07-12 18:35:20 +01:00
"storj.io/storj/satellite/satellitedb/satellitedbtest"
"storj.io/storj/storage"
)
2019-08-26 11:19:02 +01:00
func TestUntilEmpty ( t * testing . T ) {
2020-01-19 16:29:15 +00:00
satellitedbtest . Run ( t , func ( ctx * testcontext . Context , t * testing . T , db satellite . DB ) {
2019-07-12 18:35:20 +01:00
repairQueue := db . RepairQueue ( )
// insert a bunch of segments
2021-06-17 16:05:04 +01:00
idsMap := make ( map [ uuid . UUID ] int )
2020-05-22 20:54:05 +01:00
for i := 0 ; i < 20 ; i ++ {
2021-06-17 16:05:04 +01:00
injuredSeg := & queue . InjuredSegment {
StreamID : testrand . UUID ( ) ,
}
alreadyInserted , err := repairQueue . Insert ( ctx , injuredSeg )
2019-07-12 18:35:20 +01:00
require . NoError ( t , err )
2020-05-22 20:54:05 +01:00
require . False ( t , alreadyInserted )
2021-06-17 16:05:04 +01:00
idsMap [ injuredSeg . StreamID ] = 0
2019-07-12 18:35:20 +01:00
}
// select segments until no more are returned, and we should get each one exactly once
for {
injuredSeg , err := repairQueue . Select ( ctx )
if err != nil {
require . True ( t , storage . ErrEmptyQueue . Has ( err ) )
break
}
2021-06-17 16:05:04 +01:00
idsMap [ injuredSeg . StreamID ] ++
2019-07-12 18:35:20 +01:00
}
2021-06-17 16:05:04 +01:00
for _ , selectCount := range idsMap {
2019-07-12 18:35:20 +01:00
assert . Equal ( t , selectCount , 1 )
}
} )
}
2019-07-22 15:10:42 +01:00
2019-08-26 11:19:02 +01:00
func TestOrder ( t * testing . T ) {
2020-01-19 16:29:15 +00:00
satellitedbtest . Run ( t , func ( ctx * testcontext . Context , t * testing . T , db satellite . DB ) {
2019-07-22 15:10:42 +01:00
repairQueue := db . RepairQueue ( )
2021-06-17 16:05:04 +01:00
nullID := testrand . UUID ( )
recentID := testrand . UUID ( )
oldID := testrand . UUID ( )
olderID := testrand . UUID ( )
2019-07-22 15:10:42 +01:00
2021-06-17 16:05:04 +01:00
for _ , streamID := range [ ] uuid . UUID { oldID , recentID , nullID , olderID } {
injuredSeg := & queue . InjuredSegment {
StreamID : streamID ,
SegmentHealth : 10 ,
}
alreadyInserted , err := repairQueue . Insert ( ctx , injuredSeg )
2019-07-22 15:10:42 +01:00
require . NoError ( t , err )
2020-05-22 20:54:05 +01:00
require . False ( t , alreadyInserted )
2019-07-22 15:10:42 +01:00
}
2020-11-30 11:03:40 +00:00
updateList := [ ] struct {
2021-06-17 16:05:04 +01:00
streamID uuid . UUID
attemptedAt time . Time
2020-11-30 11:03:40 +00:00
} {
2021-06-17 16:05:04 +01:00
{ recentID , time . Now ( ) } ,
{ oldID , time . Now ( ) . Add ( - 7 * time . Hour ) } ,
{ olderID , time . Now ( ) . Add ( - 8 * time . Hour ) } ,
2020-11-30 11:03:40 +00:00
}
for _ , item := range updateList {
2021-06-17 16:05:04 +01:00
rowsAffected , err := db . RepairQueue ( ) . TestingSetAttemptedTime ( ctx ,
item . streamID , metabase . SegmentPosition { } , item . attemptedAt )
2020-11-30 11:03:40 +00:00
require . NoError ( t , err )
require . EqualValues ( t , 1 , rowsAffected )
}
2019-07-22 15:10:42 +01:00
2021-06-17 16:05:04 +01:00
// segment with attempted = null should be selected first
2019-07-22 15:10:42 +01:00
injuredSeg , err := repairQueue . Select ( ctx )
require . NoError ( t , err )
2021-06-17 16:05:04 +01:00
assert . Equal ( t , nullID , injuredSeg . StreamID )
2019-07-22 15:10:42 +01:00
2021-06-17 16:05:04 +01:00
// segment with attempted = 8 hours ago should be selected next
2019-07-22 15:10:42 +01:00
injuredSeg , err = repairQueue . Select ( ctx )
require . NoError ( t , err )
2021-06-17 16:05:04 +01:00
assert . Equal ( t , olderID , injuredSeg . StreamID )
2019-07-22 15:10:42 +01:00
2021-06-17 16:05:04 +01:00
// segment with attempted = 7 hours ago should be selected next
2019-07-22 15:10:42 +01:00
injuredSeg , err = repairQueue . Select ( ctx )
require . NoError ( t , err )
2021-06-17 16:05:04 +01:00
assert . Equal ( t , oldID , injuredSeg . StreamID )
2019-07-22 15:10:42 +01:00
2021-06-17 16:05:04 +01:00
// segment should be considered "empty" now
2019-07-22 15:10:42 +01:00
injuredSeg , err = repairQueue . Select ( ctx )
assert . True ( t , storage . ErrEmptyQueue . Has ( err ) )
assert . Nil ( t , injuredSeg )
} )
}
2019-07-30 16:21:40 +01:00
2020-12-02 15:45:33 +00:00
// TestOrderHealthyPieces ensures that we select in the correct order, accounting for segment health as well as last attempted repair time. We only test on Postgres since Cockraoch doesn't order by segment health due to performance.
2020-02-21 21:32:05 +00:00
func TestOrderHealthyPieces ( t * testing . T ) {
2020-12-02 15:45:33 +00:00
testorderHealthyPieces ( t , pgtest . PickPostgres ( t ) )
}
2020-02-21 21:32:05 +00:00
2020-12-02 15:45:33 +00:00
func testorderHealthyPieces ( t * testing . T , connStr string ) {
ctx := testcontext . New ( t )
defer ctx . Cleanup ( )
// create tempDB
tempDB , err := tempdb . OpenUnique ( ctx , connStr , "orderhealthy" )
require . NoError ( t , err )
defer func ( ) { require . NoError ( t , tempDB . Close ( ) ) } ( )
// create a new satellitedb connection
db , err := satellitedb . Open ( ctx , zaptest . NewLogger ( t ) , tempDB . ConnStr , satellitedb . Options { ApplicationName : "satellite-repair-test" } )
require . NoError ( t , err )
defer func ( ) { require . NoError ( t , db . Close ( ) ) } ( )
require . NoError ( t , db . MigrateToLatest ( ctx ) )
repairQueue := db . RepairQueue ( )
// we insert (path, segmentHealth, lastAttempted) as follows:
2021-06-17 16:05:04 +01:00
// ("a", 6, now-8h)
// ("b", 7, now)
// ("c", 8, null)
// ("d", 9, null)
// ("e", 9, now-7h)
// ("f", 9, now-8h)
// ("g", 10, null)
// ("h", 10, now-8h)
2020-12-02 15:45:33 +00:00
// insert the 8 segments according to the plan above
injuredSegList := [ ] struct {
2021-06-17 16:05:04 +01:00
streamID uuid . UUID
2020-12-02 15:45:33 +00:00
segmentHealth float64
attempted time . Time
} {
2021-06-17 16:05:04 +01:00
{ uuid . UUID { 'a' } , 6 , time . Now ( ) . Add ( - 8 * time . Hour ) } ,
{ uuid . UUID { 'b' } , 7 , time . Now ( ) } ,
{ uuid . UUID { 'c' } , 8 , time . Time { } } ,
{ uuid . UUID { 'd' } , 9 , time . Time { } } ,
{ uuid . UUID { 'e' } , 9 , time . Now ( ) . Add ( - 7 * time . Hour ) } ,
{ uuid . UUID { 'f' } , 9 , time . Now ( ) . Add ( - 8 * time . Hour ) } ,
{ uuid . UUID { 'g' } , 10 , time . Time { } } ,
{ uuid . UUID { 'h' } , 10 , time . Now ( ) . Add ( - 8 * time . Hour ) } ,
2020-12-02 15:45:33 +00:00
}
// shuffle list since select order should not depend on insert order
rand . Seed ( time . Now ( ) . UnixNano ( ) )
rand . Shuffle ( len ( injuredSegList ) , func ( i , j int ) {
injuredSegList [ i ] , injuredSegList [ j ] = injuredSegList [ j ] , injuredSegList [ i ]
} )
for _ , item := range injuredSegList {
// first, insert the injured segment
2021-06-17 16:05:04 +01:00
injuredSeg := & queue . InjuredSegment {
StreamID : item . streamID ,
SegmentHealth : item . segmentHealth ,
}
alreadyInserted , err := repairQueue . Insert ( ctx , injuredSeg )
2020-12-02 15:45:33 +00:00
require . NoError ( t , err )
require . False ( t , alreadyInserted )
2020-02-21 21:32:05 +00:00
2020-12-02 15:45:33 +00:00
// next, if applicable, update the "attempted at" timestamp
if ! item . attempted . IsZero ( ) {
2021-06-17 16:05:04 +01:00
rowsAffected , err := db . RepairQueue ( ) . TestingSetAttemptedTime ( ctx , item . streamID , metabase . SegmentPosition { } , item . attempted )
2020-02-21 21:32:05 +00:00
require . NoError ( t , err )
2020-12-02 15:45:33 +00:00
require . EqualValues ( t , 1 , rowsAffected )
2020-02-21 21:32:05 +00:00
}
2020-12-02 15:45:33 +00:00
}
// we expect segment health to be prioritized first
// if segment health is equal, we expect the least recently attempted, with nulls first, to be prioritized first
// (excluding segments that have been attempted in the past six hours)
// we do not expect to see segments that have been attempted in the past hour
// therefore, the order of selection should be:
2021-06-17 16:05:04 +01:00
// "a", "c", "d", "f", "e", "g", "h"
// "b" will not be selected because it was attempted recently
for _ , nextID := range [ ] uuid . UUID {
{ 'a' } ,
{ 'c' } ,
{ 'd' } ,
{ 'f' } ,
{ 'e' } ,
{ 'g' } ,
{ 'h' } ,
2020-12-02 15:45:33 +00:00
} {
2020-02-21 21:32:05 +00:00
injuredSeg , err := repairQueue . Select ( ctx )
2020-12-02 15:45:33 +00:00
require . NoError ( t , err )
2021-06-17 16:05:04 +01:00
assert . Equal ( t , nextID , injuredSeg . StreamID )
2020-12-02 15:45:33 +00:00
}
// queue should be considered "empty" now
injuredSeg , err := repairQueue . Select ( ctx )
assert . True ( t , storage . ErrEmptyQueue . Has ( err ) )
assert . Nil ( t , injuredSeg )
2020-02-21 21:32:05 +00:00
}
// TestOrderOverwrite ensures that re-inserting the same segment with a lower health, will properly adjust its prioritizationTestOrderOverwrite ensures that re-inserting the same segment with a lower health, will properly adjust its prioritization.
func TestOrderOverwrite ( t * testing . T ) {
satellitedbtest . Run ( t , func ( ctx * testcontext . Context , t * testing . T , db satellite . DB ) {
repairQueue := db . RepairQueue ( )
2021-06-17 16:05:04 +01:00
// insert segment A with segment health 10
// insert segment B with segment health 9
// re-insert segment A with segment segment health 8
// when we select, expect segment A first since after the re-insert, it is the least durable segment.
2020-02-21 21:32:05 +00:00
2021-06-17 16:05:04 +01:00
segmentA := uuid . UUID { 1 }
segmentB := uuid . UUID { 2 }
2020-02-21 21:32:05 +00:00
// insert the 8 segments according to the plan above
injuredSegList := [ ] struct {
2021-06-17 16:05:04 +01:00
streamID uuid . UUID
2020-10-21 23:02:54 +01:00
segmentHealth float64
2020-02-21 21:32:05 +00:00
} {
2021-06-17 16:05:04 +01:00
{ segmentA , 10 } ,
{ segmentB , 9 } ,
{ segmentA , 8 } ,
2020-02-21 21:32:05 +00:00
}
2020-05-22 20:54:05 +01:00
for i , item := range injuredSegList {
2021-06-17 16:05:04 +01:00
injuredSeg := & queue . InjuredSegment {
StreamID : item . streamID ,
SegmentHealth : item . segmentHealth ,
}
alreadyInserted , err := repairQueue . Insert ( ctx , injuredSeg )
2020-02-21 21:32:05 +00:00
require . NoError ( t , err )
2020-05-22 20:54:05 +01:00
if i == 2 {
require . True ( t , alreadyInserted )
} else {
require . False ( t , alreadyInserted )
}
2020-02-21 21:32:05 +00:00
}
2021-06-17 16:05:04 +01:00
for _ , nextStreamID := range [ ] uuid . UUID {
segmentA ,
segmentB ,
2020-02-21 21:32:05 +00:00
} {
injuredSeg , err := repairQueue . Select ( ctx )
require . NoError ( t , err )
2021-06-17 16:05:04 +01:00
assert . Equal ( t , nextStreamID , injuredSeg . StreamID )
2020-02-21 21:32:05 +00:00
}
// queue should be considered "empty" now
injuredSeg , err := repairQueue . Select ( ctx )
assert . True ( t , storage . ErrEmptyQueue . Has ( err ) )
assert . Nil ( t , injuredSeg )
} )
}
2019-08-26 11:19:02 +01:00
func TestCount ( t * testing . T ) {
2020-01-19 16:29:15 +00:00
satellitedbtest . Run ( t , func ( ctx * testcontext . Context , t * testing . T , db satellite . DB ) {
2019-07-30 16:21:40 +01:00
repairQueue := db . RepairQueue ( )
// insert a bunch of segments
2020-05-22 20:54:05 +01:00
numSegments := 20
2019-07-30 16:21:40 +01:00
for i := 0 ; i < numSegments ; i ++ {
2021-06-17 16:05:04 +01:00
injuredSeg := & queue . InjuredSegment {
StreamID : testrand . UUID ( ) ,
}
alreadyInserted , err := repairQueue . Insert ( ctx , injuredSeg )
2019-07-30 16:21:40 +01:00
require . NoError ( t , err )
2020-05-22 20:54:05 +01:00
require . False ( t , alreadyInserted )
2019-07-30 16:21:40 +01:00
}
count , err := repairQueue . Count ( ctx )
require . NoError ( t , err )
require . Equal ( t , count , numSegments )
} )
}