Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System-test IPv6 support #1506

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 8,18 @@ parameters:
default: 'rqlite/circleci-primary:0.0.4'

commands: # a reusable command with parameters
enable_ipv6:
steps:
- run:
name: enable ipv6
command: |
cat <<'EOF' | sudo tee /etc/docker/daemon.json
{
"ipv6": true,
"fixed-cidr-v6": "2001:db8:1::/64"
}
EOF
sudo service docker restart
restore_and_save_cache:
steps:
- restore_cache:
Expand Down Expand Up @@ -58,6 70,7 @@ jobs:
steps:
- checkout
- restore_and_save_cache
- enable_ipv6
- run: go test -failfast -v $(go list ./... | sed -n 'n;p')
resource_class: large

Expand All @@ -67,6 80,7 @@ jobs:
steps:
- checkout
- restore_and_save_cache
- enable_ipv6
- run: go test -failfast -v $(go list ./... | sed -n 'p;n')
resource_class: large

Expand All @@ -76,6 90,7 @@ jobs:
steps:
- checkout
- restore_and_save_cache
- enable_ipv6
- run:
command: go test -failfast -timeout 20m -race $(go list ./... | sed -n 'n;p')
environment:
Expand All @@ -88,6 103,7 @@ jobs:
steps:
- checkout
- restore_and_save_cache
- enable_ipv6
- run:
command: go test -failfast -timeout 20m -race $(go list ./... | sed -n 'p;n')
environment:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 2,7 @@
### Implementation changes and bug fixes
- [PR #1503](https://github.com/rqlite/rqlite/pull/1503): Use SQLite-style help in rqlite shell.
- [PR #1505](https://github.com/rqlite/rqlite/pull/1505): Correct handling of IPv6 addresses in rqlite shell.
- [PR #1506](https://github.com/rqlite/rqlite/pull/1506): Add system-level testing of IPv6 support.

## 8.12.3 (December 19th 2023)
### Implementation changes and bug fixes
Expand Down
139 changes: 139 additions & 0 deletions system_test/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 166,145 @@ func Test_MultiNodeCluster(t *testing.T) {
}
}

// Test_MultiNodeClusterIPv6 tests formation of a 3-node cluster, and its operation, when the nodes
// are using IPv6 addresses.
func Test_MultiNodeCluster_IPv6(t *testing.T) {
node1 := mustNewNodeIPv6(true)
if _, err := node1.WaitForLeader(); err != nil {
t.Fatalf("failed waiting for leader: %s", err.Error())
}
defer node1.Deprovision()

node2 := mustNewNodeIPv6(false)
defer node2.Deprovision()
if err := node2.Join(node1); err != nil {
t.Fatalf("node failed to join leader: %s", err.Error())
}
_, err := node2.WaitForLeader()
if err != nil {
t.Fatalf("failed waiting for leader: %s", err.Error())
}

// Get the new leader, in case it changed.
c := Cluster{node1, node2}
leader, err := c.Leader()
if err != nil {
t.Fatalf("failed to find cluster leader: %s", err.Error())
}

// Get a follower and confirm redirects work properly.
followers, err := c.Followers()
if err != nil {
t.Fatalf("failed to get followers: %s", err.Error())
}
if len(followers) != 1 {
t.Fatalf("got incorrect number of followers: %d", len(followers))
}

node3 := mustNewNodeIPv6(false)
defer node3.Deprovision()
if err := node3.Join(leader); err != nil {
t.Fatalf("node failed to join leader: %s", err.Error())
}
_, err = node3.WaitForLeader()
if err != nil {
t.Fatalf("failed waiting for leader: %s", err.Error())
}

// Get the new leader, in case it changed.
c = Cluster{node1, node2, node3}
leader, err = c.Leader()
if err != nil {
t.Fatalf("failed to find cluster leader: %s", err.Error())
}

// Run queries against cluster.
tests := []struct {
stmt string
expected string
execute bool
}{
{
stmt: `CREATE TABLE foo (id integer not null primary key, name text)`,
expected: `{"results":[{}]}`,
execute: true,
},
{
stmt: `INSERT INTO foo(name) VALUES("fiona")`,
expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
execute: true,
},
{
stmt: `SELECT * FROM foo`,
expected: `{"results":[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]}`,
execute: false,
},
}

for i, tt := range tests {
var r string
var err error
if tt.execute {
r, err = leader.Execute(tt.stmt)
} else {
r, err = leader.Query(tt.stmt)
}
if err != nil {
t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
}
if r != tt.expected {
t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
}
}

// Kill the leader and wait for the new leader.
leader.Deprovision()
c = c.RemoveNode(leader)
leader, err = c.WaitForNewLeader(leader)
if err != nil {
t.Fatalf("failed to find new cluster leader after killing leader: %s", err.Error())
}

// Run queries against the now 2-node cluster.
tests = []struct {
stmt string
expected string
execute bool
}{
{
stmt: `CREATE TABLE foo (id integer not null primary key, name text)`,
expected: `{"results":[{"error":"table foo already exists"}]}`,
execute: true,
},
{
stmt: `INSERT INTO foo(name) VALUES("sinead")`,
expected: `{"results":[{"last_insert_id":2,"rows_affected":1}]}`,
execute: true,
},
{
stmt: `SELECT * FROM foo`,
expected: `{"results":[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"],[2,"sinead"]]}]}`,
execute: false,
},
}

for i, tt := range tests {
var r string
var err error
if tt.execute {
r, err = leader.Execute(tt.stmt)
} else {
r, err = leader.Query(tt.stmt)
}
if err != nil {
t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
}
if r != tt.expected {
t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
}
}
}

// Test_MultiNodeClusterRANDOM tests operation of RANDOM() SQL rewriting. It checks that a rewritten
// statement is sent to follower.
func Test_MultiNodeClusterRANDOM(t *testing.T) {
Expand Down
9 changes: 9 additions & 0 deletions system_test/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 629,15 @@ func mustNewNode(enableSingle bool) *Node {
return mustNewNodeEncrypted(enableSingle, false, false)
}

func mustNewNodeIPv6(enableSingle bool) *Node {
dir := mustTempDir()
var mux *tcp.Mux
mux, _ = mustNewOpenMux("[::1]:0")
go mux.Serve()

return mustNodeEncrypted(dir, enableSingle, false, mux, "")
}

func mustNewNodeEncrypted(enableSingle, httpEncrypt, nodeEncrypt bool) *Node {
dir := mustTempDir()
var mux *tcp.Mux
Expand Down
27 changes: 27 additions & 0 deletions system_test/single_node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 156,33 @@ func Test_SingleNode(t *testing.T) {
}
}

// Test_SingleNodeBasicEndpoint_IPv6 performs a very simple check that IPv6 works.
func Test_SingleNode_IPv6(t *testing.T) {
node := mustNewNodeIPv6(true)
if _, err := node.WaitForLeader(); err != nil {
t.Fatalf("node never became leader")
}
defer node.Deprovision()

// Ensure accessing endpoints in basic manner works
_, err := node.Status()
if err != nil {
t.Fatalf(`failed to retrieve status: %s`, err)
}

_, err = node.Execute(`CREATE TABLE foo (id integer not null primary key, name text)`)
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
r, err := node.Query("SELECT * FROM foo")
if err != nil {
t.Fatalf("failed to execute query: %s", err.Error())
}
if r != `{"results":[{"columns":["id","name"],"types":["integer","text"]}]}` {
t.Fatalf("test received wrong result got %s", r)
}
}

func Test_SingleNodeRequest(t *testing.T) {
node := mustNewLeaderNode()
defer node.Deprovision()
Expand Down