github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/client/host_queue_aggregate_test.go (about) 1 // Copyright (c) 2019 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package client 22 23 import ( 24 "fmt" 25 "sync" 26 "testing" 27 "time" 28 29 "github.com/m3db/m3/src/dbnode/generated/thrift/rpc" 30 xtest "github.com/m3db/m3/src/x/test" 31 32 "github.com/golang/mock/gomock" 33 "github.com/stretchr/testify/assert" 34 "github.com/stretchr/testify/require" 35 "github.com/uber/tchannel-go/thrift" 36 ) 37 38 func TestHostQueueDrainOnCloseAggregate(t *testing.T) { 39 ctrl := gomock.NewController(xtest.Reporter{T: t}) 40 defer ctrl.Finish() 41 42 mockConnPool := NewMockconnectionPool(ctrl) 43 44 opts := newHostQueueTestOptions() 45 queue := newTestHostQueue(opts) 46 queue.connPool = mockConnPool 47 48 // Open 49 mockConnPool.EXPECT().Open() 50 queue.Open() 51 assert.Equal(t, statusOpen, queue.status) 52 53 // Prepare callback for writes 54 var ( 55 results []hostQueueResult 56 wg sync.WaitGroup 57 ) 58 callback := func(r interface{}, err error) { 59 results = append(results, hostQueueResult{r, err}) 60 wg.Done() 61 } 62 63 // Prepare aggregates 64 aggregate := testAggregateOp("testNs", callback) 65 66 mockClient := rpc.NewMockTChanNode(ctrl) 67 aggregateExec := func(ctx thrift.Context, req *rpc.AggregateQueryRawRequest) { 68 assert.Equal(t, aggregate.request.NameSpace, req.NameSpace) 69 } 70 mockClient.EXPECT().AggregateRaw(gomock.Any(), gomock.Any()).Do(aggregateExec).Return(nil, nil) 71 mockConnPool.EXPECT().NextClient().Return(mockClient, &noopPooledChannel{}, nil) 72 mockConnPool.EXPECT().Close().AnyTimes() 73 74 // Execute aggregate 75 wg.Add(1) 76 assert.NoError(t, queue.Enqueue(aggregate)) 77 78 // Close the queue should cause all writes to be flushed 79 queue.Close() 80 closeCh := make(chan struct{}) 81 go func() { 82 // Wait for all writes 83 wg.Wait() 84 close(closeCh) 85 }() 86 87 select { 88 case <-closeCh: 89 case <-time.After(time.Minute): 90 assert.Fail(t, "Not flushing writes") 91 } 92 93 // Assert aggregate successful 94 assert.Equal(t, 1, len(results)) 95 for _, result := range results { 96 assert.Nil(t, result.err) 97 } 98 } 99 100 func TestHostQueueAggregate(t *testing.T) { 101 namespace := "testNs" 102 res := &rpc.AggregateQueryRawResult_{ 103 Results: []*rpc.AggregateQueryRawResultTagNameElement{ 104 { 105 TagName: []byte("tagName"), 106 }, 107 }, 108 Exhaustive: true, 109 } 110 expectedResults := []hostQueueResult{ 111 { 112 result: aggregateResultAccumulatorOpts{ 113 response: res, 114 host: h, 115 }, 116 }, 117 } 118 testHostQueueAggregate(t, namespace, res, expectedResults, nil, func(results []hostQueueResult) { 119 assert.Equal(t, expectedResults, results) 120 }) 121 } 122 123 func TestHostQueueAggregateErrorOnNextClientUnavailable(t *testing.T) { 124 namespace := "testNs" 125 expectedErr := fmt.Errorf("an error") 126 expectedResults := []hostQueueResult{ 127 { 128 result: aggregateResultAccumulatorOpts{ 129 host: h, 130 }, 131 err: expectedErr, 132 }, 133 } 134 opts := &testHostQueueAggregateOptions{ 135 nextClientErr: expectedErr, 136 } 137 testHostQueueAggregate(t, namespace, nil, expectedResults, opts, func(results []hostQueueResult) { 138 assert.Equal(t, expectedResults, results) 139 }) 140 } 141 142 func TestHostQueueAggregateErrorOnAggregateError(t *testing.T) { 143 namespace := "testNs" 144 expectedErr := fmt.Errorf("an error") 145 expectedResults := []hostQueueResult{ 146 { 147 result: aggregateResultAccumulatorOpts{host: h}, 148 err: expectedErr, 149 }, 150 } 151 opts := &testHostQueueAggregateOptions{ 152 aggregateErr: expectedErr, 153 } 154 testHostQueueAggregate(t, namespace, nil, expectedResults, opts, func(results []hostQueueResult) { 155 assert.Equal(t, expectedResults, results) 156 }) 157 } 158 159 type testHostQueueAggregateOptions struct { 160 nextClientErr error 161 aggregateErr error 162 } 163 164 func testHostQueueAggregate( 165 t *testing.T, 166 namespace string, 167 result *rpc.AggregateQueryRawResult_, 168 expected []hostQueueResult, 169 testOpts *testHostQueueAggregateOptions, 170 assertion func(results []hostQueueResult), 171 ) { 172 ctrl := gomock.NewController(t) 173 defer ctrl.Finish() 174 175 mockConnPool := NewMockconnectionPool(ctrl) 176 177 opts := newHostQueueTestOptions(). 178 SetHostQueueOpsFlushInterval(time.Millisecond) 179 queue := newTestHostQueue(opts) 180 queue.connPool = mockConnPool 181 182 // Open 183 mockConnPool.EXPECT().Open() 184 queue.Open() 185 assert.Equal(t, statusOpen, queue.status) 186 187 // Prepare callback for aggregates 188 var ( 189 results []hostQueueResult 190 wg sync.WaitGroup 191 ) 192 callback := func(r interface{}, err error) { 193 results = append(results, hostQueueResult{r, err}) 194 wg.Done() 195 } 196 197 // Prepare aggregate op 198 aggregateOp := testAggregateOp("testNs", callback) 199 wg.Add(1) 200 201 // Prepare mocks for flush 202 mockClient := rpc.NewMockTChanNode(ctrl) 203 if testOpts != nil && testOpts.nextClientErr != nil { 204 mockConnPool.EXPECT().NextClient().Return(nil, nil, testOpts.nextClientErr) 205 } else if testOpts != nil && testOpts.aggregateErr != nil { 206 aggregateExec := func(ctx thrift.Context, req *rpc.AggregateQueryRawRequest) { 207 require.NotNil(t, req) 208 assert.Equal(t, aggregateOp.request, *req) 209 } 210 mockClient.EXPECT(). 211 AggregateRaw(gomock.Any(), gomock.Any()). 212 Do(aggregateExec). 213 Return(nil, testOpts.aggregateErr) 214 215 mockConnPool.EXPECT().NextClient().Return(mockClient, &noopPooledChannel{}, nil) 216 } else { 217 aggregateExec := func(ctx thrift.Context, req *rpc.AggregateQueryRawRequest) { 218 require.NotNil(t, req) 219 assert.Equal(t, aggregateOp.request, *req) 220 } 221 mockClient.EXPECT(). 222 AggregateRaw(gomock.Any(), gomock.Any()). 223 Do(aggregateExec). 224 Return(result, nil) 225 226 mockConnPool.EXPECT().NextClient().Return(mockClient, &noopPooledChannel{}, nil) 227 } 228 229 // Fetch 230 assert.NoError(t, queue.Enqueue(aggregateOp)) 231 232 // Wait for aggregate to complete 233 wg.Wait() 234 235 // Assert results match expected 236 assertion(results) 237 238 // Close 239 var closeWg sync.WaitGroup 240 closeWg.Add(1) 241 mockConnPool.EXPECT().Close().Do(func() { 242 closeWg.Done() 243 }) 244 queue.Close() 245 closeWg.Wait() 246 } 247 248 func testAggregateOp( 249 namespace string, 250 completionFn completionFn, 251 ) *aggregateOp { 252 f := newAggregateOp(nil) 253 f.incRef() 254 f.context = testContext() 255 f.request = rpc.AggregateQueryRawRequest{ 256 NameSpace: []byte(namespace), 257 } 258 f.completionFn = completionFn 259 return f 260 }