Skip to content

Commit c3d8222

Browse files
committed
ING-1399: Accurately test gracefuil shutdown behaviour
1 parent d5621b4 commit c3d8222

3 files changed

Lines changed: 127 additions & 4 deletions

File tree

.github/test.yml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
name: Run Integration Tests
2+
permissions:
3+
contents: read
4+
packages: read
5+
6+
on:
7+
push:
8+
tags:
9+
- v*
10+
branches:
11+
- master
12+
pull_request:
13+
jobs:
14+
test:
15+
name: Test
16+
strategy:
17+
matrix:
18+
server:
19+
- 8.1.0-1262
20+
- 8.0.0
21+
- 7.6.8
22+
- 7.2.8
23+
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v4
27+
with:
28+
submodules: recursive
29+
- name: Install cbdinocluster
30+
uses: ./.github/actions/install-cbdinocluster
31+
with:
32+
github-token: ${{ secrets.GITHUB_TOKEN }}
33+
- name: Start couchbase cluster
34+
id: start-cluster
35+
uses: ./.github/actions/start-couchbase-cluster
36+
- name: Install Stellar Gateway
37+
uses: actions/checkout@v4
38+
with:
39+
repository: couchbase/stellar-gateway
40+
submodules: recursive
41+
- uses: actions/setup-go@v5
42+
with:
43+
go-version: 1.24
44+
- uses: arduino/setup-protoc@v3
45+
with:
46+
version: 31.1
47+
repo-token: ${{ secrets.GITHUB_TOKEN }}
48+
- name: Install Tools
49+
run: |
50+
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36
51+
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5
52+
go install github.com/matryer/moq@v0.5
53+
- name: Install Dependencies
54+
run: go get ./...
55+
- name: Generate Files
56+
run: |
57+
go generate ./...
58+
59+
60+
61+
- name: Run Tests
62+
timeout-minutes: 10
63+
env:
64+
SGTEST_CBCONNSTR: ${{ steps.start-cluster.outputs.node-ip }}
65+
run: go test -v $(go list ./... | grep -v /contrib/)
66+
67+
- name: Collect couchbase logs
68+
timeout-minutes: 10
69+
if: failure()
70+
run: |
71+
mkdir -p ./logs
72+
cbdinocluster -v collect-logs ${{ steps.start-cluster.outputs.dino-id }} ./logs
73+
- name: Upload couchbase logs
74+
if: failure()
75+
uses: actions/upload-artifact@v4
76+
with:
77+
name: cbcollect-logs-${{ matrix.server }}
78+
path: ./logs/*
79+
retention-days: 5

.github/workflows/test.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ jobs:
5656
SGTEST_CBCONNSTR: ${{ steps.start-cluster.outputs.node-ip }}
5757
run: go test -v $(go list ./... | grep -v /contrib/)
5858

59+
- name: Run Graceful Shutdown Test X 100
60+
timeout-minutes: 30
61+
env:
62+
SGTEST_CBCONNSTR: ${{ steps.start-cluster.outputs.node-ip }}
63+
run: go test ./gateway/test -run TestGatewayOps -testify.m TestGracefulShutdown -count 100 -timeout 30m
64+
5965
- name: Collect couchbase logs
6066
timeout-minutes: 10
6167
if: failure()

gateway/test/dapi_graceful_shutdown_test.go

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ package test
33
import (
44
"context"
55
"crypto/tls"
6+
"errors"
67
"fmt"
8+
"io"
79
"net/http"
10+
"net/http/httptrace"
811
"sync"
912
"syscall"
1013
"time"
@@ -69,19 +72,51 @@ func (s *GatewayOpsTestSuite) TestGracefulShutdown() {
6972
},
7073
}
7174

75+
var requestsWritten int
76+
var responsesReceived int
77+
trace := &httptrace.ClientTrace{
78+
WroteRequest: func(info httptrace.WroteRequestInfo) {
79+
requestsWritten++
80+
},
81+
GotFirstResponseByte: func() {
82+
responsesReceived++
83+
},
84+
}
85+
86+
ctx := httptrace.WithClientTrace(context.Background(), trace)
87+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://%s/v1/callerIdentity", dapiAddr), nil)
88+
assert.NoError(s.T(), err)
89+
7290
respCloseChan := make(chan (bool), 10000)
7391
var wg sync.WaitGroup
7492

93+
var eofs int
94+
var unexpectedErr error
7595
wg.Add(1)
7696
go func() {
7797
defer wg.Done()
7898

7999
for {
80-
resp, err := dapiCli.Get(fmt.Sprintf("https://%s/v1/callerIdentity", dapiAddr))
100+
resp, err := dapiCli.Do(req)
81101
if err != nil {
82-
// A non-nil error should be caused by sending requests to the gateway
83-
// after it has already shutdown.
84-
assert.ErrorIs(s.T(), err, syscall.ECONNREFUSED)
102+
switch {
103+
case errors.Is(err, io.EOF):
104+
// There is a small window between the flushing the request bytes to the socket and the http handler receiving
105+
// the bytes where the server sees the connection as idle and will close it. We record these errors and
106+
// include them when checking all requests recieved responses.
107+
eofs++
108+
case errors.Is(err, syscall.ECONNREFUSED):
109+
// This is what we expect to see once the listeners have closed
110+
case errors.Is(err, syscall.ECONNRESET), errors.Is(err, syscall.EPIPE):
111+
// Connection reset and broken pipe errors occur before the request is completely written, therefore the
112+
// wroteRequest hook has not triggered. Such racy errors are unavoidable when running directly against an
113+
// http server.
114+
default:
115+
// Any errors not mentinoned above are not expected and should cause a failure to be investigated.
116+
unexpectedErr = err
117+
return
118+
}
119+
85120
return
86121
}
87122

@@ -97,6 +132,9 @@ func (s *GatewayOpsTestSuite) TestGracefulShutdown() {
97132

98133
wg.Wait()
99134

135+
assert.NoError(s.T(), unexpectedErr)
136+
assert.Equal(s.T(), requestsWritten, responsesReceived+eofs)
137+
100138
isFirstResponse := true
101139
keepAlivesDisabled := false
102140
for len(respCloseChan) > 0 {

0 commit comments

Comments
 (0)