This is an automated email from the ASF dual-hosted git repository.
hulk pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks-controller.git
The following commit(s) were added to refs/heads/unstable by this push:
new fa7e9d1 Introduce etcd's raft to implement the KV store (#222)
fa7e9d1 is described below
commit fa7e9d160e0cd4fbc272d02770401771367bcd82
Author: hulk <[email protected]>
AuthorDate: Fri Dec 6 16:31:09 2024 +0800
Introduce etcd's raft to implement the KV store (#222)
Currently, the controller must depend on external services like
zookeeper/etcd to store its data. And it's not so friendly for users
since they need to set up another cluster before running the controller
server. To simplify this process, we would like to introduce the raft
engine to save and replicate the data.
---
.github/workflows/ci.yaml | 6 +-
config/config.go | 3 +
go.mod | 38 ++-
go.sum | 148 +++++------
server/server.go | 12 +-
store/engine/engine.go | 4 +-
store/engine/raft/config.go | 62 +++++
store/engine/raft/config_test.go | 59 +++++
store/engine/raft/node.go | 526 +++++++++++++++++++++++++++++++++++++++
store/engine/raft/node_test.go | 290 +++++++++++++++++++++
store/engine/raft/store.go | 227 +++++++++++++++++
store/engine/raft/store_test.go | 101 ++++++++
store/store.go | 1 +
13 files changed, 1366 insertions(+), 111 deletions(-)
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 8552770..5845856 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -27,7 +27,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v3
with:
- go-version: 1.19
+ go-version: 1.22
- name: Checkout Code Base
uses: actions/checkout@v3
@@ -50,7 +50,7 @@ jobs:
run: |
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
- curl -sSfL
https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh
-s -- -b $(go env GOPATH)/bin v1.52.2
+ curl -sSfL
https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh
-s -- -b $(go env GOPATH)/bin v1.62.0
make lint
build-test:
@@ -58,7 +58,7 @@ jobs:
needs: [lint]
strategy:
matrix:
- go-version: [1.19, oldstable, stable]
+ go-version: [1.22, stable]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
diff --git a/config/config.go b/config/config.go
index 8916e48..80b820d 100644
--- a/config/config.go
+++ b/config/config.go
@@ -26,6 +26,8 @@ import (
"os"
"strings"
+ "github.com/apache/kvrocks-controller/store/engine/raft"
+
"github.com/go-playground/validator/v10"
"github.com/apache/kvrocks-controller/logger"
@@ -53,6 +55,7 @@ type Config struct {
StorageType string `yaml:"storage_type"`
Etcd *etcd.Config `yaml:"etcd"`
Zookeeper *zookeeper.Config `yaml:"zookeeper"`
+ Raft *raft.Config `yaml:"raft"`
Admin AdminConfig `yaml:"admin"`
Controller *ControllerConfig `yaml:"controller"`
}
diff --git a/go.mod b/go.mod
index d03e7a5..77d8437 100644
--- a/go.mod
+++ b/go.mod
@@ -1,8 +1,11 @@
module github.com/apache/kvrocks-controller
-go 1.19
+go 1.22
+
+toolchain go1.22.9
require (
+ github.com/fatih/color v1.16.0
github.com/gin-gonic/gin v1.9.1
github.com/go-playground/validator/v10 v10.14.0
github.com/go-redis/redis/v8 v8.11.5
@@ -11,9 +14,12 @@ require (
github.com/olekukonko/tablewriter v0.0.5
github.com/prometheus/client_golang v1.11.1
github.com/spf13/cobra v1.8.0
- github.com/stretchr/testify v1.8.3
+ github.com/stretchr/testify v1.9.0
go.etcd.io/etcd v3.3.27+incompatible
- go.etcd.io/etcd/client/v3 v3.5.4
+ go.etcd.io/etcd/client/pkg/v3 v3.5.17
+ go.etcd.io/etcd/client/v3 v3.5.17
+ go.etcd.io/etcd/raft/v3 v3.5.17
+ go.etcd.io/etcd/server/v3 v3.5.17
go.uber.org/atomic v1.7.0
go.uber.org/zap v1.21.0
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0
@@ -22,7 +28,7 @@ require (
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
- github.com/cespare/xxhash/v2 v2.1.2 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 //
indirect
github.com/coreos/etcd v3.3.27+incompatible // indirect
github.com/coreos/go-semver v0.3.0 // indirect
@@ -31,17 +37,18 @@ require (
github.com/coreos/pkg v0.0.0-20230327231512-ba87abf18a23 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f //
indirect
- github.com/fatih/color v1.16.0 // indirect
+ github.com/dustin/go-humanize v1.0.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
- github.com/golang/protobuf v1.5.2 // indirect
+ github.com/golang/protobuf v1.5.4 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
+ github.com/kr/pretty v0.3.1 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@@ -54,19 +61,26 @@ require (
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.26.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
+ github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
- go.etcd.io/etcd/api/v3 v3.5.4 // indirect
- go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect
+ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 //
indirect
+ go.etcd.io/etcd/api/v3 v3.5.17 // indirect
+ go.etcd.io/etcd/pkg/v3 v3.5.17 // indirect
+ go.uber.org/goleak v1.3.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
- golang.org/x/net v0.22.0 // indirect
+ golang.org/x/net v0.23.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
- google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c //
indirect
- google.golang.org/grpc v1.38.0 // indirect
- google.golang.org/protobuf v1.30.0 // indirect
+ golang.org/x/time v0.5.0 // indirect
+ google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d //
indirect
+ google.golang.org/genproto/googleapis/api
v0.0.0-20230822172742-b8732ec3820d // indirect
+ google.golang.org/genproto/googleapis/rpc
v0.0.0-20230822172742-b8732ec3820d // indirect
+ google.golang.org/grpc v1.59.0 // indirect
+ google.golang.org/protobuf v1.33.0 // indirect
+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 89003fb..9420fb6 100644
--- a/go.sum
+++ b/go.sum
@@ -1,12 +1,9 @@
-cloud.google.com/go v0.26.0/go.mod
h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod
h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-github.com/BurntSushi/toml v0.3.1/go.mod
h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod
h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod
h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod
h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod
h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod
h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
-github.com/antihax/optional v1.0.0/go.mod
h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/benbjohnson/clock v1.1.0
h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod
h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod
h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -16,16 +13,14 @@ github.com/beorn7/perks v1.0.1/go.mod
h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bytedance/sonic v1.5.0/go.mod
h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1
h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod
h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
-github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod
h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod
h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/cespare/xxhash/v2 v2.1.2
h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
-github.com/cespare/xxhash/v2 v2.1.2/go.mod
h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0
h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod
h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod
h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311
h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod
h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
-github.com/client9/misspell v0.3.4/go.mod
h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod
h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
-github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod
h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cockroachdb/datadriven v1.0.2
h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA=
+github.com/cockroachdb/datadriven v1.0.2/go.mod
h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
github.com/coreos/etcd v3.3.27+incompatible
h1:QIudLb9KeBsE5zyYxd1mjzRSkzLg9Wf9QlRwFgd6oTA=
github.com/coreos/etcd v3.3.27+incompatible/go.mod
h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0
h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
@@ -37,23 +32,20 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod
h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/coreos/pkg v0.0.0-20230327231512-ba87abf18a23
h1:SrdboTJZnOqc2r4cT4wQCzQJjGYwkclLwx2sPrDsx7g=
github.com/coreos/pkg v0.0.0-20230327231512-ba87abf18a23/go.mod
h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod
h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/creack/pty v1.1.9/go.mod
h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1
h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f
h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod
h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/dustin/go-humanize v1.0.0
h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod
h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/envoyproxy/go-control-plane v0.9.0/go.mod
h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane
v0.9.1-0.20191026205805-5f8ba28d4473/go.mod
h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.4/go.mod
h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
-github.com/envoyproxy/go-control-plane
v0.9.9-0.20210217033140-668b12f5399d/go.mod
h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod
h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod
h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fsnotify/fsnotify v1.4.9
h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fsnotify/fsnotify v1.4.9/go.mod
h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gabriel-vasile/mimetype v1.4.2
h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod
h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
-github.com/ghodss/yaml v1.0.0/go.mod
h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0
h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod
h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
@@ -65,6 +57,7 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod
h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod
h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod
h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-playground/assert/v2 v2.2.0
h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod
h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1
h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod
h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1
h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
@@ -84,35 +77,26 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod
h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
github.com/gogo/protobuf v1.1.1/go.mod
h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod
h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod
h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/mock v1.1.1/go.mod
h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod
h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod
h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod
h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod
h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod
h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod
h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod
h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod
h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod
h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.1/go.mod
h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod
h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod
h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/golang/protobuf v1.5.0/go.mod
h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2
h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
-github.com/golang/protobuf v1.5.2/go.mod
h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/google/go-cmp v0.2.0/go.mod
h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/golang/protobuf v1.5.4
h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod
h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.3.0/go.mod
h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod
h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.0/go.mod
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod
h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod
h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/uuid v1.1.2/go.mod
h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod
h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod
h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/inconshreveable/mousetrap v1.1.0
h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod
h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jpillora/backoff v1.0.0/go.mod
h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
@@ -131,18 +115,19 @@ github.com/klauspost/cpuid/v2 v2.2.4/go.mod
h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8t
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod
h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod
h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod
h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod
h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod
h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod
h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod
h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod
h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.4
h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod
h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/mattn/go-colorable v0.1.13
h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod
h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod
h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
-github.com/mattn/go-isatty v0.0.19
h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
-github.com/mattn/go-isatty v0.0.19/go.mod
h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20
h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod
h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9
h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
@@ -159,12 +144,16 @@ github.com/modern-go/reflect2 v1.0.2/go.mod
h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod
h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod
h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod
h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/olekukonko/tablewriter v0.0.5
h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod
h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
+github.com/onsi/ginkgo v1.16.5/go.mod
h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
+github.com/onsi/gomega v1.18.1/go.mod
h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/pelletier/go-toml/v2 v2.0.8
h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod
h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod
h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod
h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod
h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -178,7 +167,6 @@ github.com/prometheus/client_golang v1.11.1
h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs
github.com/prometheus/client_golang v1.11.1/go.mod
h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod
h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod
h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod
h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0
h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod
h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod
h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
@@ -190,7 +178,9 @@ github.com/prometheus/procfs v0.0.2/go.mod
h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
github.com/prometheus/procfs v0.1.3/go.mod
h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0
h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod
h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/rogpeppe/fastuuid v1.2.0/go.mod
h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rogpeppe/go-internal v1.9.0/go.mod
h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/rogpeppe/go-internal v1.10.0
h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.10.0/go.mod
h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday/v2 v2.1.0/go.mod
h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.2.0/go.mod
h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod
h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@@ -206,37 +196,45 @@ github.com/stretchr/objx v0.5.0/go.mod
h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
github.com/stretchr/testify v1.2.2/go.mod
h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod
h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod
h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.5.1/go.mod
h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod
h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod
h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod
h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod
h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod
h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.3
h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod
h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0
h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod
h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1
h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod
h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11
h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod
h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2
h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod
h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.27/go.mod
h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod
h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod
h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod
h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/etcd v3.3.27+incompatible
h1:5hMrpf6REqTHV2LW2OclNpRtxI0k9ZplMemJsMSWju0=
go.etcd.io/etcd v3.3.27+incompatible/go.mod
h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
-go.etcd.io/etcd/api/v3 v3.5.4 h1:OHVyt3TopwtUQ2GKdd5wu3PmmipR4FTwCqoEjSyRdIc=
-go.etcd.io/etcd/api/v3 v3.5.4/go.mod
h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
-go.etcd.io/etcd/client/pkg/v3 v3.5.4
h1:lrneYvz923dvC14R54XcA7FXoZ3mlGZAgmwhfm7HqOg=
-go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod
h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
-go.etcd.io/etcd/client/v3 v3.5.4
h1:p83BUL3tAYS0OT/r0qglgc3M1JjhM0diV8DSWAhVXv4=
-go.etcd.io/etcd/client/v3 v3.5.4/go.mod
h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
+go.etcd.io/etcd/api/v3 v3.5.17 h1:cQB8eb8bxwuxOilBpMJAEo8fAONyrdXTHUNcMd8yT1w=
+go.etcd.io/etcd/api/v3 v3.5.17/go.mod
h1:d1hvkRuXkts6PmaYk2Vrgqbv7H4ADfAKhyJqHNLJCB4=
+go.etcd.io/etcd/client/pkg/v3 v3.5.17
h1:XxnDXAWq2pnxqx76ljWwiQ9jylbpC4rvkAeRVOUKKVw=
+go.etcd.io/etcd/client/pkg/v3 v3.5.17/go.mod
h1:4DqK1TKacp/86nJk4FLQqo6Mn2vvQFBmruW3pP14H/w=
+go.etcd.io/etcd/client/v3 v3.5.17
h1:o48sINNeWz5+pjy/Z0+HKpj/xSnBkuVhVvXkjEXbqZY=
+go.etcd.io/etcd/client/v3 v3.5.17/go.mod
h1:j2d4eXTHWkT2ClBgnnEPm/Wuu7jsqku41v9DZ3OtjQo=
+go.etcd.io/etcd/pkg/v3 v3.5.17 h1:1k2wZ+oDp41jrk3F9o15o8o7K3/qliBo0mXqxo1PKaE=
+go.etcd.io/etcd/pkg/v3 v3.5.17/go.mod
h1:FrztuSuaJG0c7RXCOzT08w+PCugh2kCQXmruNYCpCGA=
+go.etcd.io/etcd/raft/v3 v3.5.17 h1:wHPW/b1oFBw/+HjDAQ9vfr17OIInejTIsmwMZpK1dNo=
+go.etcd.io/etcd/raft/v3 v3.5.17/go.mod
h1:uapEfOMPaJ45CqBYIraLO5+fqyIY2d57nFfxzFwy4D4=
+go.etcd.io/etcd/server/v3 v3.5.17
h1:xykBwLZk9IdDsB8z8rMdCCPRvhrG+fwvARaGA0TRiyc=
+go.etcd.io/etcd/server/v3 v3.5.17/go.mod
h1:40sqgtGt6ZJNKm8nk8x6LexZakPu+NDl/DCgZTZ69Cc=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod
h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod
h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod
h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod
h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
-go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod
h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
@@ -250,30 +248,21 @@ golang.org/x/crypto
v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.19.0/go.mod
h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod
h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod
h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod
h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod
h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod
h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod
h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod
h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod
h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod
h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod
h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod
h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod
h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod
h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod
h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod
h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod
h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod
h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod
h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod
h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod
h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod
h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod
h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod
h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
@@ -281,12 +270,10 @@ golang.org/x/net
v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
-golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod
h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
+golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod
h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod
h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -296,7 +283,6 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod
h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -310,7 +296,6 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod
h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -333,7 +318,6 @@ golang.org/x/term v0.18.0/go.mod
h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
@@ -342,62 +326,47 @@ golang.org/x/text v0.14.0/go.mod
h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod
h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod
h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod
h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod
h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod
h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod
h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod
h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.2/go.mod
h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod
h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod
h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod
h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/appengine v1.1.0/go.mod
h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod
h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod
h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod
h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod
h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod
h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c
h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0=
-google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod
h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
-google.golang.org/grpc v1.19.0/go.mod
h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.23.0/go.mod
h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.25.1/go.mod
h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
-google.golang.org/grpc v1.27.0/go.mod
h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.33.1/go.mod
h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
-google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
-google.golang.org/grpc v1.38.0/go.mod
h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d
h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY=
+google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod
h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4=
+google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d
h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q=
+google.golang.org/genproto/googleapis/api
v0.0.0-20230822172742-b8732ec3820d/go.mod
h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d
h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
+google.golang.org/genproto/googleapis/rpc
v0.0.0-20230822172742-b8732ec3820d/go.mod
h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
+google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
+google.golang.org/grpc v1.59.0/go.mod
h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod
h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod
h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod
h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod
h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod
h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
-google.golang.org/protobuf v1.22.0/go.mod
h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod
h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod
h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.25.0/go.mod
h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod
h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0/go.mod
h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.30.0
h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
-google.golang.org/protobuf v1.30.0/go.mod
h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.33.0
h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+google.golang.org/protobuf v1.33.0/go.mod
h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod
h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15
h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod
h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod
h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0
h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod
h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -408,7 +377,4 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod
h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod
h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod
h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod
h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
-sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/server/server.go b/server/server.go
index b4b8a08..fc99e43 100644
--- a/server/server.go
+++ b/server/server.go
@@ -28,6 +28,8 @@ import (
"strings"
"time"
+ "github.com/apache/kvrocks-controller/store/engine/raft"
+
"github.com/gin-gonic/gin"
"github.com/apache/kvrocks-controller/config"
@@ -53,13 +55,17 @@ func NewServer(cfg *config.Config) (*Server, error) {
var err error
sessionID := helper.GenerateSessionID(cfg.Addr)
- switch {
- case strings.EqualFold(cfg.StorageType, "etcd"):
+ storageType := strings.ToLower(cfg.StorageType)
+ switch storageType {
+ case "etcd":
logger.Get().Info("Use Etcd as store")
persist, err = etcd.New(sessionID, cfg.Etcd)
- case strings.EqualFold(cfg.StorageType, "zookeeper"):
+ case "zookeeper":
logger.Get().Info("Use Zookeeper as store")
persist, err = zookeeper.New(sessionID, cfg.Zookeeper)
+ case "raft":
+ logger.Get().Info("Use Raft as store")
+ persist, err = raft.New(cfg.Raft)
default:
logger.Get().Info("Use Etcd as default store")
persist, err = etcd.New(sessionID, cfg.Etcd)
diff --git a/store/engine/engine.go b/store/engine/engine.go
index 9b95594..b25685c 100644
--- a/store/engine/engine.go
+++ b/store/engine/engine.go
@@ -24,8 +24,8 @@ import (
)
type Entry struct {
- Key string
- Value []byte
+ Key string `json:"key"`
+ Value []byte `json:"value"`
}
type Engine interface {
diff --git a/store/engine/raft/config.go b/store/engine/raft/config.go
new file mode 100644
index 0000000..3ddf059
--- /dev/null
+++ b/store/engine/raft/config.go
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package raft
+
+import "errors"
+
+type Config struct {
+ // ID is the identity of the local raft. ID cannot be 0.
+ ID uint64
+ // DataDir is the directory to store the raft data which includes
snapshot and WALs.
+ DataDir string
+ // Join should be set to true if the node is joining an existing
cluster.
+ Join bool
+
+ // Peers is the list of raft peers.
+ Peers []string
+ HeartbeatSeconds int
+ ElectionSeconds int
+}
+
+func (c *Config) validate() error {
+ if c.ID == 0 {
+ return errors.New("ID cannot be 0")
+ }
+ if len(c.Peers) == 0 {
+ return errors.New("peers cannot be empty")
+ }
+ if c.ID > uint64(len(c.Peers)) {
+ return errors.New("ID cannot be greater than the number of
peers")
+ }
+ return nil
+}
+
+func (c *Config) init() {
+ if c.DataDir == "" {
+ c.DataDir = "."
+ }
+ if c.HeartbeatSeconds == 0 {
+ c.HeartbeatSeconds = 2
+ }
+ if c.ElectionSeconds == 0 {
+ c.ElectionSeconds = c.HeartbeatSeconds * 10
+ }
+}
diff --git a/store/engine/raft/config_test.go b/store/engine/raft/config_test.go
new file mode 100644
index 0000000..88ea00d
--- /dev/null
+++ b/store/engine/raft/config_test.go
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package raft
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestConfig_Validate(t *testing.T) {
+ c := &Config{}
+
+ // missing ID
+ require.ErrorContains(t, c.validate(), "ID cannot be 0")
+ // missing peers
+ c.ID = 1
+ require.ErrorContains(t, c.validate(), "peers cannot be empty")
+ // valid
+ c.Peers = []string{"http://127.0.0.1:12345"}
+ require.NoError(t, c.validate())
+ // ID greater than the number of peers
+ c.ID = 2
+ require.ErrorContains(t, c.validate(), "ID cannot be greater than the
number of peers")
+}
+
+func TestConfig_Init(t *testing.T) {
+ c := &Config{}
+ c.init()
+ require.Equal(t, ".", c.DataDir)
+ require.Equal(t, 2, c.HeartbeatSeconds)
+ require.Equal(t, 20, c.ElectionSeconds)
+
+ c.DataDir = "/tmp"
+ c.HeartbeatSeconds = 3
+ c.ElectionSeconds = 30
+ c.init()
+ require.Equal(t, "/tmp", c.DataDir)
+ require.Equal(t, 3, c.HeartbeatSeconds)
+ require.Equal(t, 30, c.ElectionSeconds)
+}
diff --git a/store/engine/raft/node.go b/store/engine/raft/node.go
new file mode 100644
index 0000000..3eed408
--- /dev/null
+++ b/store/engine/raft/node.go
@@ -0,0 +1,526 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package raft
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "os"
+ "sync"
+ "time"
+
+ "github.com/apache/kvrocks-controller/logger"
+ "github.com/apache/kvrocks-controller/store/engine"
+
+ "go.etcd.io/etcd/client/pkg/v3/types"
+ "go.etcd.io/etcd/raft/v3"
+ "go.etcd.io/etcd/raft/v3/raftpb"
+ "go.etcd.io/etcd/server/v3/etcdserver/api/rafthttp"
+ stats "go.etcd.io/etcd/server/v3/etcdserver/api/v2stats"
+
+ "go.uber.org/atomic"
+ "go.uber.org/zap"
+)
+
+const (
+ defaultSnapshotThreshold = 10000
+ defaultCompactThreshold = 1024
+)
+
+const (
+ opGet = iota + 1
+ opSet
+ opDelete
+)
+
+type Event struct {
+ Op int `json:"op"`
+ Key string `json:"key"`
+ Value []byte `json:"value"`
+}
+
+type Node struct {
+ config *Config
+
+ addr string
+ raftNode raft.Node
+ transport *rafthttp.Transport
+ httpServer *http.Server
+ dataStore *DataStore
+ leaderChanged chan bool
+ logger *zap.Logger
+ peers sync.Map
+
+ mu sync.Mutex
+ leader uint64
+ appliedIndex uint64
+ snapshotIndex uint64
+ confState raftpb.ConfState
+ snapshotThreshold uint64
+ compactThreshold uint64
+
+ wg sync.WaitGroup
+ shutdown chan struct{}
+
+ isRunning atomic.Bool
+}
+
+var _ engine.Engine = (*Node)(nil)
+
+func New(config *Config) (*Node, error) {
+ config.init()
+ if err := config.validate(); err != nil {
+ return nil, err
+ }
+
+ logger := logger.Get().With(zap.Uint64("node_id", config.ID))
+ n := &Node{
+ config: config,
+ leader: raft.None,
+ dataStore: NewDataStore(config.DataDir),
+ leaderChanged: make(chan bool),
+ snapshotThreshold: defaultSnapshotThreshold,
+ compactThreshold: defaultCompactThreshold,
+ logger: logger,
+ }
+ if err := n.run(); err != nil {
+ return nil, err
+ }
+ return n, nil
+}
+
+func (n *Node) Addr() string {
+ return n.addr
+}
+
+func (n *Node) Peers() []string {
+ peers := make([]string, 0)
+ n.peers.Range(func(key, value interface{}) bool {
+ peer, _ := value.(string)
+ peers = append(peers, peer)
+ return true
+ })
+ return peers
+}
+
+func (n *Node) SetSnapshotThreshold(threshold uint64) {
+ n.mu.Lock()
+ defer n.mu.Unlock()
+ n.snapshotThreshold = threshold
+}
+
+func (n *Node) run() error {
+ // The node is already running
+ if !n.isRunning.CAS(false, true) {
+ return nil
+ }
+ n.shutdown = make(chan struct{})
+
+ peers := make([]raft.Peer, len(n.config.Peers))
+ for i, peer := range n.config.Peers {
+ peers[i] = raft.Peer{
+ ID: uint64(i + 1),
+ Context: []byte(peer),
+ }
+ }
+ raftConfig := &raft.Config{
+ ID: n.config.ID,
+ HeartbeatTick: n.config.HeartbeatSeconds,
+ ElectionTick: n.config.ElectionSeconds,
+ MaxInflightMsgs: 128,
+ MaxSizePerMsg: 10 * 1024 * 1024, // 10 MiB
+ Storage: n.dataStore.raftStorage,
+ }
+
+ // WAL existing check must be done before replayWAL since it will
create a new WAL if not exists
+ walExists := n.dataStore.walExists()
+ if err := n.dataStore.replayWAL(); err != nil {
+ return err
+ }
+
+ if n.config.Join || walExists {
+ n.raftNode = raft.RestartNode(raftConfig)
+ } else {
+ n.raftNode = raft.StartNode(raftConfig, peers)
+ }
+
+ if err := n.runTransport(); err != nil {
+ return err
+ }
+ return n.runRaftMessages()
+}
+
+func (n *Node) runTransport() error {
+ logger := logger.Get()
+ idString := fmt.Sprintf("%d", n.config.ID)
+ transport := &rafthttp.Transport{
+ ID: types.ID(n.config.ID),
+ Logger: logger,
+ ClusterID: 0x6666,
+ Raft: n,
+ LeaderStats: stats.NewLeaderStats(logger, idString),
+ ServerStats: stats.NewServerStats("raft", idString),
+ ErrorC: make(chan error),
+ }
+ if err := transport.Start(); err != nil {
+ return fmt.Errorf("unable to start transport: %w", err)
+ }
+ for i, peer := range n.config.Peers {
+ // Don't add self to transport
+ if uint64(i+1) != n.config.ID {
+ transport.AddPeer(types.ID(i+1), []string{peer})
+ }
+ n.peers.Store(uint64(i+1), peer)
+ }
+
+ n.addr = n.config.Peers[n.config.ID-1]
+ url, err := url.Parse(n.addr)
+ if err != nil {
+ return err
+ }
+ httpServer := &http.Server{
+ Addr: url.Host,
+ Handler: transport.Handler(),
+ }
+
+ n.wg.Add(1)
+ go func() {
+ defer n.wg.Done()
+ if err := httpServer.ListenAndServe(); err != nil &&
!errors.Is(err, http.ErrServerClosed) {
+ n.logger.Fatal("Unable to start http server",
zap.Error(err))
+ os.Exit(1)
+ }
+ }()
+
+ n.transport = transport
+ n.httpServer = httpServer
+ return nil
+}
+
+func (n *Node) runRaftMessages() error {
+ snapshot, err := n.dataStore.loadSnapshotFromDisk()
+ if err != nil {
+ return err
+ }
+
+ // Load the snapshot into the key-value store.
+ if err := n.dataStore.reloadSnapshot(); err != nil {
+ return err
+ }
+ n.appliedIndex = snapshot.Metadata.Index
+ n.snapshotIndex = snapshot.Metadata.Index
+ n.confState = snapshot.Metadata.ConfState
+
+ n.wg.Add(1)
+ go func() {
+ ticker := time.NewTicker(100 * time.Millisecond)
+ defer func() {
+ ticker.Stop()
+ n.wg.Done()
+ }()
+
+ for {
+ select {
+ case <-ticker.C:
+ n.raftNode.Tick()
+ case rd := <-n.raftNode.Ready():
+ // Save to wal and storage first
+ if !raft.IsEmptySnap(rd.Snapshot) {
+ if err :=
n.dataStore.saveSnapshot(rd.Snapshot); err != nil {
+ n.logger.Error("Failed to save
snapshot", zap.Error(err))
+ }
+ }
+ if err := n.dataStore.wal.Save(rd.HardState,
rd.Entries); err != nil {
+ n.logger.Error("Failed to save to wal",
zap.Error(err))
+ }
+
+ // Replay the entries into the raft storage
+ if err := n.applySnapshot(rd.Snapshot); err !=
nil {
+ n.logger.Error("Failed to apply
snapshot", zap.Error(err))
+ }
+ if len(rd.Entries) > 0 {
+ _ =
n.dataStore.raftStorage.Append(rd.Entries)
+ }
+
+ for _, msg := range rd.Messages {
+ if msg.Type == raftpb.MsgApp {
+ msg.Snapshot.Metadata.ConfState
= n.confState
+ }
+ }
+ n.transport.Send(rd.Messages)
+
+ // Apply the committed entries to the state
machine
+ n.applyEntries(rd.CommittedEntries)
+ if err := n.triggerSnapshotIfNeed(); err != nil
{
+ n.logger.Error("Failed to trigger
snapshot", zap.Error(err))
+ }
+ n.raftNode.Advance()
+ case err := <-n.transport.ErrorC:
+ n.logger.Fatal("Found transport error",
zap.Error(err))
+ return
+ case <-n.shutdown:
+ n.logger.Info("Shutting down raft node")
+ return
+ }
+ }
+ }()
+ return nil
+}
+
+func (n *Node) triggerSnapshotIfNeed() error {
+ if n.appliedIndex-n.snapshotIndex <= n.snapshotThreshold {
+ return nil
+ }
+ snapshotBytes, err := n.dataStore.GetDataStoreSnapshot()
+ if err != nil {
+ return err
+ }
+ snap, err := n.dataStore.raftStorage.CreateSnapshot(n.appliedIndex,
&n.confState, snapshotBytes)
+ if err != nil {
+ return err
+ }
+ if err := n.dataStore.saveSnapshot(snap); err != nil {
+ return err
+ }
+
+ compactIndex := uint64(1)
+ if n.appliedIndex > n.compactThreshold {
+ compactIndex = n.appliedIndex - n.compactThreshold
+ }
+ if err := n.dataStore.raftStorage.Compact(compactIndex); err != nil &&
!errors.Is(err, raft.ErrCompacted) {
+ return err
+ }
+ n.snapshotIndex = n.appliedIndex
+ return nil
+}
+
+func (n *Node) Set(ctx context.Context, key string, value []byte) error {
+ bytes, err := json.Marshal(&Event{
+ Op: opSet,
+ Key: key,
+ Value: value,
+ })
+ if err != nil {
+ return err
+ }
+ return n.raftNode.Propose(ctx, bytes)
+}
+
+func (n *Node) AddPeer(ctx context.Context, nodeID uint64, peer string) error {
+ cc := raftpb.ConfChange{
+ Type: raftpb.ConfChangeAddNode,
+ NodeID: nodeID,
+ Context: []byte(peer),
+ }
+ return n.raftNode.ProposeConfChange(ctx, cc)
+}
+
+func (n *Node) RemovePeer(ctx context.Context, nodeID uint64) error {
+ cc := raftpb.ConfChange{
+ Type: raftpb.ConfChangeRemoveNode,
+ NodeID: nodeID,
+ }
+ return n.raftNode.ProposeConfChange(ctx, cc)
+}
+
+func (n *Node) ID() string {
+ return fmt.Sprintf("%d", n.config.ID)
+}
+
+func (n *Node) Leader() string {
+ return fmt.Sprintf("%d", n.GetRaftLead())
+}
+
+func (n *Node) GetRaftLead() uint64 {
+ return n.raftNode.Status().Lead
+}
+
+func (n *Node) IsReady(_ context.Context) bool {
+ return n.raftNode.Status().Lead != raft.None
+}
+
+func (n *Node) LeaderChange() <-chan bool {
+ return n.leaderChanged
+}
+
+func (n *Node) Get(_ context.Context, key string) ([]byte, error) {
+ return n.dataStore.Get(key)
+}
+
+func (n *Node) Exists(_ context.Context, key string) (bool, error) {
+ _, err := n.dataStore.Get(key)
+ if err != nil {
+ if errors.Is(err, ErrKeyNotFound) {
+ return false, nil
+ }
+ return false, err
+ }
+ return true, nil
+}
+
+func (n *Node) Delete(ctx context.Context, key string) error {
+ bytes, err := json.Marshal(&Event{
+ Op: opDelete,
+ Key: key,
+ })
+ if err != nil {
+ return err
+ }
+ return n.raftNode.Propose(ctx, bytes)
+}
+
+func (n *Node) List(_ context.Context, prefix string) ([]engine.Entry, error) {
+ n.dataStore.List(prefix)
+ return nil, nil
+}
+
+func (n *Node) applySnapshot(snapshot raftpb.Snapshot) error {
+ if raft.IsEmptySnap(snapshot) {
+ return nil
+ }
+
+ _ = n.dataStore.raftStorage.ApplySnapshot(snapshot)
+ if n.appliedIndex >= snapshot.Metadata.Index {
+ return fmt.Errorf("snapshot index [%d] should be greater than
applied index [%d]", snapshot.Metadata.Index, n.appliedIndex)
+ }
+
+ // Load the snapshot into the key-value store.
+ if err := n.dataStore.reloadSnapshot(); err != nil {
+ return err
+ }
+ n.confState = snapshot.Metadata.ConfState
+ n.appliedIndex = snapshot.Metadata.Index
+ n.snapshotIndex = snapshot.Metadata.Index
+ return nil
+}
+
+func (n *Node) applyEntries(entries []raftpb.Entry) {
+ if len(entries) == 0 || entries[0].Index > n.appliedIndex+1 {
+ return
+ }
+
+ firstEntryIndex := entries[0].Index
+ // remove entries that have been applied
+ if n.appliedIndex-firstEntryIndex+1 < uint64(len(entries)) {
+ entries = entries[n.appliedIndex-firstEntryIndex+1:]
+ }
+ for _, entry := range entries {
+ if err := n.applyEntry(entry); err != nil {
+ n.logger.Error("failed to apply entry", zap.Error(err))
+ }
+ }
+ n.appliedIndex = entries[len(entries)-1].Index
+}
+
+func (n *Node) applyEntry(entry raftpb.Entry) error {
+ switch entry.Type {
+ case raftpb.EntryNormal:
+ // apply entry to the state machine
+ if len(entry.Data) == 0 {
+ // empty message, skip it.
+ return nil
+ }
+
+ var e Event
+ if err := json.Unmarshal(entry.Data, &e); err != nil {
+ return err
+ }
+ switch e.Op {
+ case opSet:
+ n.dataStore.Set(e.Key, e.Value)
+ return nil
+ case opDelete:
+ n.dataStore.Delete(e.Key)
+ case opGet:
+ // do nothing
+ default:
+ return fmt.Errorf("unknown operation type: %d", e.Op)
+ }
+ case raftpb.EntryConfChangeV2, raftpb.EntryConfChange:
+ // apply config change to the state machine
+ var cc raftpb.ConfChange
+ if err := cc.Unmarshal(entry.Data); err != nil {
+ return err
+ }
+
+ n.confState = *n.raftNode.ApplyConfChange(cc)
+ switch cc.Type {
+ case raftpb.ConfChangeAddNode:
+ if cc.NodeID != n.config.ID && len(cc.Context) > 0 {
+ n.logger.Info("Add the new peer",
zap.String("context", string(cc.Context)))
+ n.transport.AddPeer(types.ID(cc.NodeID),
[]string{string(cc.Context)})
+ n.peers.Store(cc.NodeID, string(cc.Context))
+ }
+ case raftpb.ConfChangeRemoveNode:
+ n.peers.Delete(cc.NodeID)
+ n.transport.RemovePeer(types.ID(cc.NodeID))
+ if cc.NodeID == n.config.ID {
+ n.Close()
+ n.logger.Info("Node removed from the cluster")
+ return nil
+ }
+ case raftpb.ConfChangeUpdateNode:
+ n.transport.UpdatePeer(types.ID(cc.NodeID),
[]string{string(cc.Context)})
+ if _, ok := n.peers.Load(cc.NodeID); ok {
+ n.peers.Store(cc.NodeID, string(cc.Context))
+ }
+ case raftpb.ConfChangeAddLearnerNode:
+ // TODO: add the learner node
+ }
+ }
+ return nil
+}
+
+func (n *Node) Process(ctx context.Context, m raftpb.Message) error {
+ return n.raftNode.Step(ctx, m)
+}
+
+func (n *Node) IsIDRemoved(_ uint64) bool {
+ return false
+}
+
+func (n *Node) ReportUnreachable(id uint64) {
+ n.raftNode.ReportUnreachable(id)
+}
+
+func (n *Node) ReportSnapshot(id uint64, status raft.SnapshotStatus) {
+ n.raftNode.ReportSnapshot(id, status)
+}
+
+func (n *Node) Close() error {
+ if !n.isRunning.CAS(true, false) {
+ return nil
+ }
+ close(n.shutdown)
+ n.raftNode.Stop()
+ n.transport.Stop()
+ if err := n.httpServer.Close(); err != nil {
+ return err
+ }
+
+ n.dataStore.Close()
+ n.wg.Wait()
+ return nil
+}
diff --git a/store/engine/raft/node_test.go b/store/engine/raft/node_test.go
new file mode 100644
index 0000000..cb60cb0
--- /dev/null
+++ b/store/engine/raft/node_test.go
@@ -0,0 +1,290 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package raft
+
+import (
+ "context"
+ "fmt"
+ "math/rand"
+ "os"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+ "go.etcd.io/etcd/raft/v3"
+)
+
+type TestCluster struct {
+ nodes []*Node
+}
+
+func NewTestCluster(n int) *TestCluster {
+ if n > 16 {
+ n = 16
+ }
+ nodes := make([]*Node, n)
+ randomStartPort := rand.Int31n(1024) + 10000
+ peers := make([]string, n)
+ for i := 0; i < n; i++ {
+ peers[i] = fmt.Sprintf("http://127.0.0.1:%d",
randomStartPort+int32(i))
+ }
+ for i := 0; i < n; i++ {
+ nodes[i], _ = New(&Config{
+ ID: uint64(i + 1),
+ DataDir: fmt.Sprintf("/tmp/kvrocks/raft/%d",
randomStartPort+int32(i)),
+ Peers: peers,
+ HeartbeatSeconds: 1,
+ ElectionSeconds: 2,
+ })
+ }
+ return &TestCluster{nodes: nodes}
+}
+
+func (c *TestCluster) createNode(peers []string) (*Node, error) {
+ randomPort := rand.Int31n(1024) + 20000
+ addr := fmt.Sprintf("http://127.0.0.1:%d", randomPort)
+ node, err := New(&Config{
+ ID: uint64(len(peers) + 1),
+ DataDir: fmt.Sprintf("/tmp/kvrocks/raft/%d",
randomPort),
+ Peers: append(peers, addr),
+ HeartbeatSeconds: 1,
+ ElectionSeconds: 2,
+ })
+ return node, err
+}
+
+func (c *TestCluster) AddNode(ctx context.Context, nodeID uint64, peer string)
error {
+ if len(c.nodes) == 0 {
+ return nil
+ }
+ return c.nodes[0].AddPeer(ctx, nodeID, peer)
+}
+
+func (c *TestCluster) RemoveNode(ctx context.Context, nodeID uint64) error {
+ for i, n := range c.nodes {
+ if n.config.ID == nodeID {
+ c.nodes = append(c.nodes[:i], c.nodes[i+1:]...)
+ break
+ }
+ }
+ if len(c.nodes) == 0 {
+ return nil
+ }
+ return c.nodes[0].RemovePeer(ctx, nodeID)
+}
+
+func (c *TestCluster) SetSnapshotThreshold(threshold uint64) {
+ for _, n := range c.nodes {
+ n.SetSnapshotThreshold(threshold)
+ }
+}
+
+func (c *TestCluster) IsReady(ctx context.Context) bool {
+ for _, n := range c.nodes {
+ if !n.IsReady(ctx) {
+ return false
+ }
+ }
+ return true
+}
+
+func (c *TestCluster) GetNode(i int) *Node {
+ if i < 0 || i >= len(c.nodes) {
+ return nil
+ }
+ return c.nodes[i]
+}
+
+func (c *TestCluster) GetLeaderNode() *Node {
+ leaderID := raft.None
+ for _, n := range c.nodes {
+ if n.GetRaftLead() == leaderID {
+ continue
+ }
+ leaderID = n.GetRaftLead()
+ }
+ if leaderID == raft.None {
+ return nil
+ }
+ return c.GetNode(int(leaderID - 1))
+}
+
+func (c *TestCluster) ListNodes() []*Node {
+ return c.nodes
+}
+
+func (c *TestCluster) Restart() error {
+ for _, n := range c.nodes {
+ n.Close()
+ }
+ for _, n := range c.nodes {
+ if err := n.run(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (c *TestCluster) Close() {
+ for _, n := range c.nodes {
+ n.Close()
+ os.RemoveAll(n.config.DataDir)
+ }
+}
+
+func TestCluster_SingleNode(t *testing.T) {
+ cluster := NewTestCluster(1)
+ defer cluster.Close()
+
+ ctx := context.Background()
+ require.Eventually(t, func() bool {
+ return cluster.IsReady(ctx)
+ }, 10*time.Second, 100*time.Millisecond)
+
+ n := cluster.GetNode(0)
+ require.NotNil(t, n)
+ require.NoError(t, n.Set(ctx, "foo", []byte("bar")))
+
+ require.Eventually(t, func() bool {
+ gotBytes, _ := n.Get(ctx, "foo")
+ return string(gotBytes) == "bar"
+ }, 1*time.Second, 100*time.Millisecond)
+}
+
+func TestCluster_MultiNodes(t *testing.T) {
+ cluster := NewTestCluster(3)
+ defer cluster.Close()
+
+ ctx := context.Background()
+ require.Eventually(t, func() bool {
+ return cluster.IsReady(ctx)
+ }, 10*time.Second, 100*time.Millisecond)
+
+ t.Run("works well with all nodes ready", func(t *testing.T) {
+ n1 := cluster.GetNode(0)
+ n2 := cluster.GetNode(1)
+ require.NoError(t, n1.Set(ctx, "foo", []byte("bar")))
+ require.Eventually(t, func() bool {
+ got, _ := n2.Get(ctx, "foo")
+ return string(got) == "bar"
+ }, 1*time.Second, 100*time.Millisecond)
+ })
+
+ t.Run("works well if 1/3 nodes down", func(t *testing.T) {
+ oldLeaderNode := cluster.GetLeaderNode()
+ require.NotNil(t, oldLeaderNode)
+ oldLeaderNode.Close()
+
+ require.Eventually(t, func() bool {
+ newLeaderNode := cluster.GetLeaderNode()
+ return newLeaderNode != nil && newLeaderNode !=
oldLeaderNode
+ }, 10*time.Second, 200*time.Millisecond)
+
+ leaderNode := cluster.GetLeaderNode()
+ require.NoError(t, leaderNode.Set(ctx, "foo", []byte("bar")))
+ })
+}
+
+func TestCluster_AddRemovePeer(t *testing.T) {
+ cluster := NewTestCluster(3)
+ defer cluster.Close()
+
+ ctx := context.Background()
+ require.Eventually(t, func() bool {
+ return cluster.IsReady(ctx)
+ }, 10*time.Second, 100*time.Millisecond)
+
+ n1 := cluster.GetNode(0)
+ require.NoError(t, n1.Set(ctx, "foo", []byte("bar")))
+ require.Eventually(t, func() bool {
+ got, _ := n1.Get(ctx, "foo")
+ return string(got) == "bar"
+ }, 1*time.Second, 100*time.Millisecond)
+
+ t.Run("add a new peer node", func(t *testing.T) {
+ n4, err := cluster.createNode(n1.config.Peers)
+ require.NoError(t, err)
+ require.NotNil(t, n4)
+
+ require.NoError(t, cluster.AddNode(ctx, n4.config.ID,
n4.Addr()))
+ require.Eventually(t, func() bool {
+ return n4.IsReady(ctx)
+ }, 10*time.Second, 100*time.Millisecond)
+
+ require.NoError(t, n4.Set(ctx, "foo", []byte("bar-1")))
+ require.Eventually(t, func() bool {
+ got, _ := n1.Get(ctx, "foo")
+ return string(got) == "bar-1"
+ }, 1*time.Second, 100*time.Millisecond)
+ require.Len(t, n1.Peers(), 4)
+ })
+
+ t.Run("remove a peer node", func(t *testing.T) {
+ cluster.RemoveNode(ctx, 4)
+ require.Eventually(t, func() bool {
+ return len(n1.Peers()) == 3
+ }, 10*time.Second, 100*time.Millisecond)
+ })
+}
+
+func TestTriggerSnapshot(t *testing.T) {
+ cluster := NewTestCluster(3)
+ defer cluster.Close()
+
+ ctx := context.Background()
+ require.Eventually(t, func() bool {
+ return cluster.IsReady(ctx)
+ }, 10*time.Second, 100*time.Millisecond)
+
+ cnt := 128
+ cluster.SetSnapshotThreshold(uint64(cnt / 5))
+
+ n := cluster.GetNode(0)
+ require.NotNil(t, n)
+ for i := 0; i < cnt; i++ {
+ require.NoError(t, n.Set(ctx, fmt.Sprintf("foo%d", i),
[]byte("bar")))
+ }
+
+ // Use Eventually to wait for snapshot to be triggered
+ require.Eventually(t, func() bool {
+ allNodesHasSnapshot := true
+ for _, n := range cluster.ListNodes() {
+ snapshot, err := n.dataStore.loadSnapshotFromDisk()
+ require.NoError(t, err)
+ if snapshot.Metadata.Index <= 0 {
+ allNodesHasSnapshot = false
+ break
+ }
+ }
+ return allNodesHasSnapshot
+ }, 10*time.Second, 100*time.Millisecond)
+
+ require.NoError(t, cluster.Restart())
+ require.Eventually(t, func() bool {
+ return cluster.IsReady(ctx)
+ }, 10*time.Second, 100*time.Millisecond)
+
+ // Can restore data from snapshot correctly after restart
+ for i := 0; i < cnt; i++ {
+ gotBytes, _ := n.Get(ctx, fmt.Sprintf("foo%d", i))
+ require.Equal(t, "bar", string(gotBytes))
+ }
+}
diff --git a/store/engine/raft/store.go b/store/engine/raft/store.go
new file mode 100644
index 0000000..4834a31
--- /dev/null
+++ b/store/engine/raft/store.go
@@ -0,0 +1,227 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package raft
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "os"
+ "strings"
+ "sync"
+
+ "github.com/apache/kvrocks-controller/logger"
+ "github.com/apache/kvrocks-controller/store/engine"
+
+ "go.etcd.io/etcd/pkg/fileutil"
+ "go.etcd.io/etcd/raft/v3"
+ "go.etcd.io/etcd/raft/v3/raftpb"
+ "go.etcd.io/etcd/server/v3/etcdserver/api/snap"
+ "go.etcd.io/etcd/server/v3/wal"
+ "go.etcd.io/etcd/server/v3/wal/walpb"
+)
+
+var ErrKeyNotFound = errors.New("key not found")
+
+type DataStore struct {
+ walDir string
+ snapshotDir string
+
+ snapshotter *snap.Snapshotter
+ wal *wal.WAL
+
+ raftStorage *raft.MemoryStorage
+
+ mu sync.RWMutex
+ kvs map[string][]byte
+}
+
+func NewDataStore(dir string) *DataStore {
+ snapshotDir := fmt.Sprintf("%s/snapshot", dir)
+ snapshotter := snap.New(logger.Get(), snapshotDir)
+ return &DataStore{
+ walDir: fmt.Sprintf("%s/wal", dir),
+ snapshotDir: snapshotDir,
+ snapshotter: snapshotter,
+ raftStorage: raft.NewMemoryStorage(),
+ kvs: make(map[string][]byte),
+ }
+}
+
+func (ds *DataStore) walExists() bool {
+ return wal.Exist(ds.walDir)
+}
+
+func (ds *DataStore) loadSnapshotFromDisk() (*raftpb.Snapshot, error) {
+ if !fileutil.Exist(ds.snapshotDir) {
+ if err := os.MkdirAll(ds.snapshotDir, 0750); err != nil {
+ return nil, err
+ }
+ }
+
+ emptySnapshot := &raftpb.Snapshot{}
+ if !ds.walExists() {
+ return emptySnapshot, nil
+ }
+
+ snapshots, err := wal.ValidSnapshotEntries(logger.Get(), ds.walDir)
+ if err != nil {
+ return nil, err
+ }
+ latestSnapshot, err := ds.snapshotter.LoadNewestAvailable(snapshots)
+ if err != nil {
+ if errors.Is(err, snap.ErrNoSnapshot) {
+ return emptySnapshot, nil
+ }
+ return nil, err
+ }
+ return latestSnapshot, nil
+}
+
+func (ds *DataStore) reloadSnapshot() error {
+ snapshot, err := ds.snapshotter.Load()
+ if errors.Is(err, snap.ErrNoSnapshot) {
+ return nil
+ }
+ if err != nil {
+ return err
+ }
+
+ var m map[string][]byte
+ if err := json.Unmarshal(snapshot.Data, &m); err != nil {
+ return err
+ }
+
+ ds.mu.Lock()
+ ds.kvs = m
+ ds.mu.Unlock()
+ return nil
+}
+
+func (ds *DataStore) openWAL(snapshot *raftpb.Snapshot) (*wal.WAL, error) {
+ if !ds.walExists() {
+ if err := os.MkdirAll(ds.walDir, 0750); err != nil {
+ return nil, err
+ }
+ w, err := wal.Create(logger.Get(), ds.walDir, nil)
+ if err != nil {
+ return nil, err
+ }
+ w.Close()
+ }
+ walSnapshot := walpb.Snapshot{}
+ if snapshot != nil {
+ walSnapshot.Index = snapshot.Metadata.Index
+ walSnapshot.Term = snapshot.Metadata.Term
+ }
+ return wal.Open(logger.Get(), ds.walDir, walSnapshot)
+}
+
+func (ds *DataStore) replayWAL() error {
+ snapshot, err := ds.loadSnapshotFromDisk()
+ if err != nil {
+ return fmt.Errorf("failed to load newest snapshot: %w", err)
+ }
+
+ w, err := ds.openWAL(snapshot)
+ if err != nil {
+ return fmt.Errorf("failed to open WAL: %w", err)
+ }
+ ds.wal = w
+
+ _, hardState, entries, err := w.ReadAll()
+ if err != nil {
+ return fmt.Errorf("failed to read WAL: %w", err)
+ }
+ if snapshot != nil {
+ _ = ds.raftStorage.ApplySnapshot(*snapshot)
+ }
+ if err := ds.raftStorage.SetHardState(hardState); err != nil {
+ return fmt.Errorf("failed to set hard state: %w", err)
+ }
+ if err := ds.raftStorage.Append(entries); err != nil {
+ return fmt.Errorf("failed to append entries: %w", err)
+ }
+ return nil
+}
+
+func (ds *DataStore) saveSnapshot(snapshot raftpb.Snapshot) error {
+ walSnap := walpb.Snapshot{
+ Index: snapshot.Metadata.Index,
+ Term: snapshot.Metadata.Term,
+ ConfState: &snapshot.Metadata.ConfState,
+ }
+ if err := ds.snapshotter.SaveSnap(snapshot); err != nil {
+ return err
+ }
+ if err := ds.wal.SaveSnapshot(walSnap); err != nil {
+ return err
+ }
+ return ds.wal.ReleaseLockTo(snapshot.Metadata.Index)
+}
+
+func (ds *DataStore) Set(key string, value []byte) {
+ ds.mu.Lock()
+ defer ds.mu.Unlock()
+ ds.kvs[key] = value
+}
+
+func (ds *DataStore) Get(key string) ([]byte, error) {
+ ds.mu.RLock()
+ defer ds.mu.RUnlock()
+ if v, ok := ds.kvs[key]; ok {
+ return v, nil
+ }
+ return nil, ErrKeyNotFound
+}
+
+func (ds *DataStore) Delete(key string) {
+ ds.mu.Lock()
+ defer ds.mu.Unlock()
+ delete(ds.kvs, key)
+}
+
+func (ds *DataStore) List(prefix string) []engine.Entry {
+ ds.mu.RLock()
+ defer ds.mu.RUnlock()
+ entries := make([]engine.Entry, 0)
+ for k := range ds.kvs {
+ if strings.HasPrefix(k, prefix) {
+ entries = append(entries, engine.Entry{
+ Key: strings.TrimPrefix(k, prefix),
+ Value: ds.kvs[k],
+ })
+ }
+ }
+ return entries
+}
+
+func (ds *DataStore) GetDataStoreSnapshot() ([]byte, error) {
+ ds.mu.RLock()
+ defer ds.mu.RUnlock()
+ return json.Marshal(ds.kvs)
+}
+
+func (ds *DataStore) Close() {
+ if ds.wal != nil {
+ ds.wal.Close()
+ }
+}
diff --git a/store/engine/raft/store_test.go b/store/engine/raft/store_test.go
new file mode 100644
index 0000000..e16e2ae
--- /dev/null
+++ b/store/engine/raft/store_test.go
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package raft
+
+import (
+ "os"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "go.etcd.io/etcd/raft/v3/raftpb"
+)
+
+func TestDataStore(t *testing.T) {
+ dir := "/tmp/kvrocks/raft/test-datastore"
+ store := NewDataStore(dir)
+ require.NotNil(t, store)
+
+ defer func() {
+ store.Close()
+ os.RemoveAll(dir)
+ }()
+
+ err := store.replayWAL()
+ require.NoError(t, err)
+
+ t.Run("reply WAL from the disk", func(t *testing.T) {
+ require.NoError(t, store.wal.Save(raftpb.HardState{Term: 1,
Vote: 1}, []raftpb.Entry{
+ {Term: 1, Index: 1, Type: raftpb.EntryNormal, Data:
[]byte("test-1")},
+ {Term: 1, Index: 2, Type: raftpb.EntryNormal, Data:
[]byte("test-2")},
+ {Term: 1, Index: 3, Type: raftpb.EntryNormal, Data:
[]byte("test-3")},
+ }))
+ store.Close()
+
+ store = NewDataStore(dir)
+ require.NoError(t, store.replayWAL())
+
+ firstIndex, err := store.raftStorage.FirstIndex()
+ require.NoError(t, err)
+ require.EqualValues(t, 1, firstIndex)
+
+ lastIndex, err := store.raftStorage.LastIndex()
+ require.NoError(t, err)
+ require.EqualValues(t, 3, lastIndex)
+
+ term, err := store.raftStorage.Term(1)
+ require.NoError(t, err)
+ require.EqualValues(t, 1, term)
+ })
+
+ t.Run("Basic GET/SET/DELETE/LIST", func(t *testing.T) {
+ store.Set("bar-1", []byte("v1"))
+ store.Set("bar-2", []byte("v2"))
+ store.Set("baz-3", []byte("v3"))
+ store.Set("ba-4", []byte("v4"))
+ store.Set("foo", []byte("v5"))
+
+ v, err := store.Get("bar-2")
+ require.NoError(t, err)
+ require.Equal(t, []byte("v2"), v)
+
+ entries := store.List("bar")
+ require.Len(t, entries, 2)
+
+ entries = store.List("baz")
+ require.Len(t, entries, 1)
+
+ entries = store.List("ba")
+ require.Len(t, entries, 4)
+
+ entries = store.List("bar-2")
+ require.Len(t, entries, 1)
+
+ entries = store.List("foo")
+ require.Len(t, entries, 1)
+
+ store.Delete("bar-2")
+ _, err = store.Get("bar-2")
+ require.ErrorIs(t, err, ErrKeyNotFound)
+
+ entries = store.List("bar")
+ require.Len(t, entries, 1)
+ })
+}
diff --git a/store/store.go b/store/store.go
index 860df19..ce730b4 100644
--- a/store/store.go
+++ b/store/store.go
@@ -17,6 +17,7 @@
* under the License.
*
*/
+
package store
import (