diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5681a5f..f03d8fb 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -26,6 +26,18 @@ jobs: redis: image: "eqalpha/keydb" ports: [ "6379:6379" ] + postgres: + image: "postgres:latest" + env: + POSTGRES_DB: test + POSTGRES_USER: postgres + POSTGRES_HOST_AUTH_METHOD: trust + ports: [ "5432:5432" ] + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: - uses: "actions/checkout@v3" - uses: "actions/setup-go@v3" diff --git a/README.md b/README.md index d8722ac..3e9458d 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Modified version of [Chihaya](https://github.com/chihaya/chihaya), an open sourc * Supports BittorrentV2 hashes (SHA-256 and _hybrid_ SHA-256-to-160 [BEP52](https://www.bittorrent.org/beps/bep_0052.html), tested with qBittorrent); * Supports storage in middleware modules to persist useful data; -* Supports [KeyDB](https://keydb.dev) storage; +* Supports [KeyDB](https://keydb.dev) and [PostgreSQL](https://www.postgresql.org) storages; * Metrics can be turned off (not enabled till it really needed); * Allows mixed peers: IPv4 requesters can fetch IPv6 peers or vice versa; * Contains some internal improvements. diff --git a/cmd/mochi/config.go b/cmd/mochi/config.go index fc2ecba..f665f5c 100644 --- a/cmd/mochi/config.go +++ b/cmd/mochi/config.go @@ -18,6 +18,7 @@ import ( // Imports to register storage drivers. _ "github.com/sot-tech/mochi/storage/keydb" _ "github.com/sot-tech/mochi/storage/memory" + _ "github.com/sot-tech/mochi/storage/pg" _ "github.com/sot-tech/mochi/storage/redis" ) diff --git a/cmd/mochi/main.go b/cmd/mochi/main.go index 788a1d5..12399dd 100644 --- a/cmd/mochi/main.go +++ b/cmd/mochi/main.go @@ -30,11 +30,11 @@ func main() { flag.Parse() if err := l.ConfigureLogger(*logOut, *logLevel, *logPretty, *logColored); err != nil { - log.Fatal("unable to configure logger ", err) + log.Fatal("unable to configure logger: ", err) } if err := s.Run(*configPath); err != nil { - log.Fatal("unable to start server ", err) + log.Fatal("unable to start server: ", err) } defer s.Dispose() ch := make(chan os.Signal, 2) diff --git a/dist/example_config.yaml b/dist/example_config.yaml index 3f762a5..766d8ea 100644 --- a/dist/example_config.yaml +++ b/dist/example_config.yaml @@ -141,10 +141,10 @@ mochi: # are collected and posted to Prometheus. prometheus_reporting_interval: 1s - # This block defines configuration used for redis storage. - #storage: - #name: redis - #config: + # This block defines configuration used for redis storage. + #storage: + #name: redis + #config: # The frequency which stale peers are removed. # This balances between # - collecting garbage more often, potentially using more CPU time, but potentially using less memory (lower value) @@ -194,6 +194,58 @@ mochi: # Dial timeout for establishing new connections. #connect_timeout: 15s + # This block defines configuration used for PostgreSQL storage. + # example `mo_peers` table structure: + # - info_hash bytea + # - peer_id bytea + # - address inet or bytea + # - port int4 + # - is_seeder bool + # - is_v6 bool + # - created timestamp + #storage: + #name: pg + #config: + # connection string to pg storage. may be URL (postgres://...) or DSN (host=... port=...) + #connection_string: host=127.0.0.1 database=test user=postgres pool_max_conns=50 + # query and parameters for announce operation + #announce: + #query: SELECT peer_id, address, port FROM mo_peers WHERE info_hash=$1 AND is_seeder=$2 AND is_v6=$3 LIMIT $4 + #peer_id_column: peer_id + #address_column: address + #port_column: port + #peer: + # expected parameters: 1 - info hash (bytea), 2 - peer id (bytea), 3 - ip address (bytea/inet) + # 4 - port (int), 5 - is seeder (bool), 6 - is IPv6 (bool), 7 - create date and time (timestamp) + #add_query: INSERT INTO mo_peers VALUES($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (info_hash, peer_id, address, port) DO UPDATE SET created = EXCLUDED.created, is_seeder = EXCLUDED.is_seeder + #del_query: DELETE FROM mo_peers WHERE info_hash=$1 AND peer_id=$2 AND address=$3 AND port=$4 AND is_seeder=$5 + #graduate_query: UPDATE mo_peers SET is_seeder=TRUE WHERE info_hash=$1 AND peer_id=$2 AND address=$3 AND port=$4 AND NOT is_seeder + #count_query: SELECT COUNT(1) FILTER (WHERE is_seeder) AS seeders, COUNT(1) FILTER (WHERE NOT is_seeder) AS leechers FROM mo_peers + # predicate part of `count_query` for get count of peers by info hash + #by_info_hash_clause: WHERE info_hash = $1 + #count_seeders_column: seeders + #count_leechers_column: leechers + # queries for KV-store + #data: + # expected parameters: 1 - context (varchar), 2 - name (bytea), 3 - value (bytea) + #add_query: INSERT INTO mo_kv VALUES($1, $2, $3) ON CONFLICT (context, name) DO NOTHING + #del_query: DELETE FROM mo_kv WHERE context=$1 AND name=$2 + #get_query: SELECT value FROM mo_kv WHERE context=$1 AND name=$2 + # query for check if database is alive + #ping_query: SELECT 1 + # query for garbage collection, expected parameter is timestamp + #gc_query: DELETE FROM mo_peers WHERE created > $1 + # The amount of time until a peer is considered stale. + # To avoid churn, keep this slightly larger than `announce_interval` + #peer_lifetime: 31m + # The frequency which stale peers are removed. + #gc_interval: 3m + # query for info hash statistics + #info_hash_count_query: SELECT COUNT(DISTINCT info_hash) as info_hashes FROM mo_peers + # The interval at which metrics about the number of info hashes and peers + # are collected and posted to Prometheus. + #prometheus_reporting_interval: 1s + # This block defines configuration used for middleware executed before a # response has been returned to a BitTorrent client. prehooks: diff --git a/docs/storage/postgres.md b/docs/storage/postgres.md new file mode 100644 index 0000000..a5a8216 --- /dev/null +++ b/docs/storage/postgres.md @@ -0,0 +1,152 @@ +# PostgreSQL Storage + +This storage uses PostgreSQL-like database to store peer and arbitrary key-value data. + +'PostgreSQL-like' means, that you can use any database which _understand_ PostgreSQL protocol +i.e. _real_ [PostgreSQL](https://www.postgresql.org) or [CockroachDB](https://www.cockroachlabs.com). + +_(YugabyteDB is not recommended (at the moment), because of some problems with +concurrent inserts while benchmarks.)_ + +# Use case + +Redis is good and fast solution for storing MoChi data, which used across multiple nodes, +but it is in-memory DB and there are some problems with it: + +1. on heavy load it consumes a lot of memory, which increased up to 3x times while snapshot (bgsave) +2. if redis reaches memory limit it just stops working +3. when HA is not used and redis instance dies, it loses all data from last snapshot + +On the other hand, PostgreSQL relies on persistent storage: +it much slower than in-memory solution (especially in insert operations), +however it can serve about 10 millions MoChi records using OOTB parameters +(128MiB shared buffers and 4MiB work memory consumption) +with suitable enough performance. + +So if you don't have a lot of RAM, but have a lot of peers/info hashes, +you might use this store type. + +## Configuration and implementation notes + +This store type relies on database structure and queries that +you provide in configuration. + +Implementation expects next data types: + +* Table for peers: + * info hash - byte array (`bytea`) + * peer ID - byte array (`bytea`) + * peer address - `inet` or byte array (`bytea`) + * peer port - integer or derivative type (`int4`, `integer`) + * is seeder - boolean (`bool`) + * is IPv6 - boolean (`bool`) + * peer creation date and time - `timestamp` +* Table for arbitrary data (KV store): + * context - string (`varchar`, `character varying`) + * name - string (`varchar`, `character varying`) + * value - byte array (`bytea`) + +Sample script to create tables: + +```sql +CREATE TABLE mo_peers +( + info_hash bytea NOT NULL, + peer_id bytea NOT NULL, + address inet NOT NULL, + port int4 NOT NULL, + is_seeder bool NOT NULL, + is_v6 bool NOT NULL, + created timestamp NOT NULL DEFAULT current_timestamp, + PRIMARY KEY (info_hash, peer_id, address, port) +); + +CREATE INDEX mo_peers_created_idx ON mo_peers (created); +CREATE INDEX mo_peers_announce_idx ON mo_peers (info_hash, is_seeder, is_v6); + +CREATE TABLE mo_kv +( + context varchar NOT NULL, + name bytea NOT NULL, + value bytea, + PRIMARY KEY (context, name) +); +``` + +_Note: CockroachDB currently does not support index +over `inet` type, but it is possible to use `bytea` instead._ + +```yaml +storage: + name: pg + config: + # Connection string to PostgreSQL. + # May be URL (postgres://...) or DSN (host=... port=...) + connection_string: host=127.0.0.1 port=5432 database=... user=... + announce: + # Query to select peers by info hash and flags + query: SELECT peer_id, address, port FROM mo_peers WHERE info_hash=$1 AND is_seeder=$2 AND is_v6=$3 LIMIT $4 + # Column name of peer id in `query` above (case-insensitive). + peer_id_column: peer_id + # Column name of address in `query` above (case-insensitive). + address_column: address + # Column name of port in `query` above (case-insensitive). + port_column: port + peer: + # Query to add peer info. + # Expected arguments: + # 1 - info hash (bytea), + # 2 - peer id (bytea), + # 3 - ip address (bytea/inet) + # 4 - port (int4), + # 5 - is seeder (bool), + # 6 - is IPv6 (bool), + # 7 - create date and time (timestamp) + # Query MUST handle situations when tuple + # `info hash - peer ID - address - port` is already + # exists in table + add_query: INSERT INTO mo_peers VALUES($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (info_hash, peer_id, address, port) DO UPDATE SET created = EXCLUDED.created, is_seeder = EXCLUDED.is_seeder + # Query to delete peer info. + # Query SHOULD take into account value of `is seeder` flag + del_query: DELETE FROM mo_peers WHERE info_hash=$1 AND peer_id=$2 AND address=$3 AND port=$4 AND is_seeder=$5 + # Query to update leecher to seeder + graduate_query: UPDATE mo_peers SET is_seeder=TRUE WHERE info_hash=$1 AND peer_id=$2 AND address=$3 AND port=$4 AND NOT is_seeder + # Query to get count of peers. + # Used both for statistics and for scrape (with clause suffix, see next). + # Only first returned row values used. + count_query: SELECT COUNT(1) FILTER (WHERE is_seeder) AS seeders, COUNT(1) FILTER (WHERE NOT is_seeder) AS leechers FROM mo_peers + # Predicate part of `count_query` for get count of peers by info hash + by_info_hash_clause: WHERE info_hash = $1 + # Column name of seeders count in `count_query` (case-insensitive). + count_seeders_column: seeders + # Column name of leechers count in `count_query` (case-insensitive). + count_leechers_column: leechers + # Queries for KV-store + data: + # Query to add data. + # Expected arguments: + # 1 - context (varchar), + # 2 - name (bytea), + # 3 - value (bytea) + add_query: INSERT INTO mo_kv VALUES($1, $2, $3) ON CONFLICT (context, name) DO NOTHING + # Query to delete data. + del_query: DELETE FROM mo_kv WHERE context=$1 AND name=$2 + # Query to get data. + # Only first returned row and column value used. + get_query: SELECT value FROM mo_kv WHERE context=$1 AND name=$2 + # Query for check if database is alive (can be omitted) + ping_query: SELECT 1 + # Query to delete stale peers (peers, which timestamp older than provided argument) + gc_query: DELETE FROM mo_peers WHERE created > $1 + # The frequency which stale peers are removed. + gc_interval: 3m + # Query to get all info hash count (used for statistics). + # Only first returned row and column value used. + info_hash_count_query: SELECT COUNT(DISTINCT info_hash) as info_hashes FROM mo_peers + # The interval at which metrics about the number of info hashes and peers + # are collected and posted to Prometheus. + prometheus_reporting_interval: 1s +``` + +You can use own database structure and queries, but queries should have +same behaviour and arguments as provided in example above. diff --git a/docs/storage/redis.md b/docs/storage/redis.md index 88a452a..9a9fd3c 100644 --- a/docs/storage/redis.md +++ b/docs/storage/redis.md @@ -35,8 +35,11 @@ mochi: # To avoid churn, keep this slightly larger than `announce_interval` peer_lifetime: 31m - # The address of redis storage. - redis_broker: "redis://pwd@127.0.0.1:6379/0" + # The addresses of redis storage. + addresses: ["127.0.0.1:6379"] + + # Database number + db: 0 # The timeout for reading a command reply from redis. read_timeout: 15s diff --git a/frontend/http/frontend.go b/frontend/http/frontend.go index f54f2f5..c4ee849 100644 --- a/frontend/http/frontend.go +++ b/frontend/http/frontend.go @@ -311,7 +311,7 @@ func (f *Frontend) scrapeRoute(w http.ResponseWriter, r *http.Request, ps httpro func (f Frontend) ping(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { var err error if r.Method == http.MethodGet { - err = f.logic.Ping(context.Background()) + err = f.logic.Ping(context.TODO()) } if err == nil { w.WriteHeader(http.StatusOK) diff --git a/go.mod b/go.mod index 7d2d955..346106d 100644 --- a/go.mod +++ b/go.mod @@ -3,18 +3,19 @@ module github.com/sot-tech/mochi go 1.18 require ( - code.cloudfoundry.org/go-diodes v0.0.0-20220420211542-53509ccdf174 + code.cloudfoundry.org/go-diodes v0.0.0-20220601181242-ac2da19efd60 github.com/SermoDigital/jose v0.9.2-0.20180104203859-803625baeddc - github.com/anacrolix/torrent v1.44.0 + github.com/anacrolix/torrent v1.45.0 github.com/go-redis/redis/v8 v8.11.5 + github.com/jackc/pgx/v4 v4.16.1 github.com/julienschmidt/httprouter v1.3.0 github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103 github.com/minio/sha256-simd v1.0.0 github.com/mitchellh/mapstructure v1.5.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.12.2 - github.com/rs/zerolog v1.26.1 - github.com/stretchr/testify v1.7.1 + github.com/rs/zerolog v1.27.0 + github.com/stretchr/testify v1.7.4 gopkg.in/yaml.v3 v3.0.1 ) @@ -31,12 +32,24 @@ require ( github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/huandu/xstrings v1.3.2 // indirect - github.com/klauspost/cpuid/v2 v2.0.12 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.12.1 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/pgtype v1.11.0 // indirect + github.com/jackc/puddle v1.2.1 // indirect + github.com/klauspost/cpuid/v2 v2.0.14 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.34.0 // indirect + github.com/prometheus/common v0.35.0 // indirect github.com/prometheus/procfs v0.7.3 // indirect - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect + golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect + golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect + golang.org/x/text v0.3.7 // indirect google.golang.org/protobuf v1.28.0 // indirect ) diff --git a/go.sum b/go.sum index c2fefea..9ad2d3c 100644 --- a/go.sum +++ b/go.sum @@ -30,13 +30,14 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -code.cloudfoundry.org/go-diodes v0.0.0-20220420211542-53509ccdf174 h1:Ht2zKWftukU3F3ACIdE8asNhso3DgHPzaCDO2K5SWmA= -code.cloudfoundry.org/go-diodes v0.0.0-20220420211542-53509ccdf174/go.mod h1:HLP7HKUU1eqMAGMk247yT91tDDi4xxnehkyXh6hGcr0= +code.cloudfoundry.org/go-diodes v0.0.0-20220601181242-ac2da19efd60 h1:hBycJRDauXKMEAOl90iFjTqrQ4VNGb918x3lLLDXBBU= +code.cloudfoundry.org/go-diodes v0.0.0-20220601181242-ac2da19efd60/go.mod h1:HLP7HKUU1eqMAGMk247yT91tDDi4xxnehkyXh6hGcr0= crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk= crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w= github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI= github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= @@ -73,8 +74,8 @@ github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQ github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8= -github.com/anacrolix/torrent v1.44.0 h1:Yl58hCsX+4O7me5oUWQphg0G46bs22hJWLdEYAq250w= -github.com/anacrolix/torrent v1.44.0/go.mod h1:SsvA8hlN3q1gC1Pf+fJ7QrfWI+5DumO6tEl4bqf+D2U= +github.com/anacrolix/torrent v1.45.0 h1:dmRfw+kSl+UtBk2gAOpJgdnu3OzWsSmE6sMTunifpdw= +github.com/anacrolix/torrent v1.45.0/go.mod h1:511SlHgyD0LAeN5CIJw8CSfS4BaBVDtfVkRSMHPjfAQ= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -94,7 +95,12 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 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/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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= @@ -111,7 +117,7 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF 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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= @@ -136,6 +142,8 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -180,7 +188,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -209,6 +217,54 @@ github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.12.1 h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8= +github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y= +github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.11.0 h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs= +github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.16.1 h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y= +github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.2.1 h1:gI8os0wpRXFd4FiAY2dWiqRK037tjj3t7rKFeO4X5iw= +github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -224,18 +280,34 @@ github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4d github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE= -github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.0.14 h1:QRqdp6bb9M9S5yyKeYteXKuoKE4p0tGlra81fKOpWH8= +github.com/klauspost/cpuid/v2 v2.0.14/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= 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.2/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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 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/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103 h1:Z/i1e+gTZrmcGeZyWckaLfucYG6KYOXLWo4co8pZYNY= @@ -290,8 +362,8 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.34.0 h1:RBmGO9d/FVjqHT0yUGQwBJhkwKV+wPCn7KGpvfab0uE= -github.com/prometheus/common v0.34.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= +github.com/prometheus/common v0.35.0 h1:Eyr+Pw2VymWejHqCugNaQXkAi6KayVNxaHeu6khmFBE= +github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -306,11 +378,19 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= -github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs= +github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -319,13 +399,17 @@ github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:X github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM= +github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= @@ -334,7 +418,7 @@ github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPy github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -342,13 +426,30 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -379,7 +480,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/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/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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -396,6 +496,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -412,7 +513,6 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -432,14 +532,15 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-20180909124046-d0be0721c37e/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-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -447,11 +548,14 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -472,12 +576,14 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -485,6 +591,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -499,14 +606,18 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -514,6 +625,7 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -532,7 +644,8 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= @@ -624,6 +737,7 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= 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.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/middleware/hooks.go b/middleware/hooks.go index e81dd70..2b1d77c 100644 --- a/middleware/hooks.go +++ b/middleware/hooks.go @@ -180,7 +180,7 @@ func (h *responseHook) appendPeers(req *bittorrent.AnnounceRequest, resp *bittor } l := len(peers) - uniquePeers := make(map[bittorrent.Peer]interface{}, l) + uniquePeers := make(map[bittorrent.Peer]any, l) resp.IPv4Peers = make([]bittorrent.Peer, 0, l/2) resp.IPv6Peers = make([]bittorrent.Peer, 0, l/2) diff --git a/pkg/log/log.go b/pkg/log/log.go index 83e96dd..798798c 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -181,14 +181,14 @@ func (l *Logger) Log() *zerolog.Event { // Print sends a log event using debug level and no extra field. // Arguments are handled in the manner of fmt.Print. -func (l *Logger) Print(v ...interface{}) { +func (l *Logger) Print(v ...any) { l.init() l.Logger.Print(v...) } // Printf sends a log event using debug level and no extra field. // Arguments are handled in the manner of fmt.Printf. -func (l *Logger) Printf(format string, v ...interface{}) { +func (l *Logger) Printf(format string, v ...any) { l.init() l.Logger.Printf(format, v...) } @@ -276,13 +276,13 @@ func Log() *zerolog.Event { // Print sends a log event using debug level and no extra field. // Arguments are handled in the manner of fmt.Print. -func Print(v ...interface{}) { +func Print(v ...any) { root.Print(v...) } // Printf sends a log event using debug level and no extra field. // Arguments are handled in the manner of fmt.Printf. -func Printf(format string, v ...interface{}) { +func Printf(format string, v ...any) { root.Printf(format, v...) } diff --git a/storage/keydb/storage.go b/storage/keydb/storage.go index b972d8a..7487850 100644 --- a/storage/keydb/storage.go +++ b/storage/keydb/storage.go @@ -12,6 +12,7 @@ package keydb import ( "context" "errors" + "time" "github.com/go-redis/redis/v8" "github.com/rs/zerolog" @@ -32,9 +33,9 @@ const ( var ( logger = log.NewLogger(Name) - // ErrNotKeyDB returned from initializer if connected does not support KeyDB + // errNotKeyDB returned from initializer if connected does not support KeyDB // specific command (EXPIREMEMBER) - ErrNotKeyDB = errors.New("provided instance seems not KeyDB") + errNotKeyDB = errors.New("provided instance seems not KeyDB") ) func init() { @@ -69,7 +70,7 @@ func newStore(cfg r.Config) (*store, error) { _ = rs.Process(context.Background(), cmd) err = r.AsNil(cmd.Err()) if err == nil && len(cmd.Val()) == 0 { - err = ErrNotKeyDB + err = errNotKeyDB } var st *store @@ -158,15 +159,15 @@ func (s store) GraduateLeecher(ih bittorrent.InfoHash, peer bittorrent.Peer) (er } // AnnouncePeers is the same function as redis.AnnouncePeers -func (s store) AnnouncePeers(ih bittorrent.InfoHash, seeder bool, numWant int, v6 bool) ([]bittorrent.Peer, error) { +func (s store) AnnouncePeers(ih bittorrent.InfoHash, forSeeder bool, numWant int, v6 bool) ([]bittorrent.Peer, error) { logger.Trace(). Stringer("infoHash", ih). - Bool("seeder", seeder). + Bool("forSeeder", forSeeder). Int("numWant", numWant). Bool("v6", v6). Msg("announce peers") - return s.GetPeers(ih, seeder, numWant, v6, func(ctx context.Context, infoHashKey string, maxCount int) *redis.StringSliceCmd { + return s.GetPeers(ih, forSeeder, numWant, v6, func(ctx context.Context, infoHashKey string, maxCount int) *redis.StringSliceCmd { return s.SRandMemberN(context.TODO(), infoHashKey, int64(maxCount)) }) } @@ -180,6 +181,18 @@ func (s store) ScrapeSwarm(ih bittorrent.InfoHash) (leechers uint32, seeders uin return } +func (store) GCAware() bool { + return false +} + +func (store) ScheduleGC(_, _ time.Duration) {} + +func (store) StatisticsAware() bool { + return false +} + +func (store) ScheduleStatisticsCollection(_ time.Duration) {} + func (s *store) Stop() stop.Result { c := make(stop.Channel) if s.UniversalClient != nil { diff --git a/storage/memory/storage.go b/storage/memory/storage.go index 1009961..381e186 100644 --- a/storage/memory/storage.go +++ b/storage/memory/storage.go @@ -390,7 +390,7 @@ func (ps *peerStore) getPeers(shard *peerShard, ih bittorrent.InfoHash, maxCount return } -func (ps *peerStore) AnnouncePeers(ih bittorrent.InfoHash, seeder bool, numWant int, v6 bool) (peers []bittorrent.Peer, err error) { +func (ps *peerStore) AnnouncePeers(ih bittorrent.InfoHash, forSeeder bool, numWant int, v6 bool) (peers []bittorrent.Peer, err error) { select { case <-ps.closed: panic("attempted to interact with stopped memory store") @@ -398,12 +398,12 @@ func (ps *peerStore) AnnouncePeers(ih bittorrent.InfoHash, seeder bool, numWant } logger.Trace(). Stringer("infoHash", ih). - Bool("seeder", seeder). + Bool("forSeeder", forSeeder). Int("numWant", numWant). Bool("v6", v6). Msg("announce peers") - peers = ps.getPeers(ps.shards[ps.shardIndex(ih, v6)], ih, numWant, seeder) + peers = ps.getPeers(ps.shards[ps.shardIndex(ih, v6)], ih, numWant, forSeeder) return } @@ -499,6 +499,14 @@ func (*dataStore) Preservable() bool { return false } +func (*peerStore) GCAware() bool { + return true +} + +func (*peerStore) StatisticsAware() bool { + return true +} + // GC deletes all Peers from the PeerStorage which are older than the // cutoff time. // diff --git a/storage/pg/storage.go b/storage/pg/storage.go new file mode 100644 index 0000000..717fa2c --- /dev/null +++ b/storage/pg/storage.go @@ -0,0 +1,588 @@ +package pg + +import ( + "context" + "errors" + "fmt" + "net" + "net/netip" + "strings" + "sync" + "time" + + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/log/zerologadapter" + "github.com/jackc/pgx/v4/pgxpool" + "github.com/rs/zerolog" + + "github.com/sot-tech/mochi/bittorrent" + "github.com/sot-tech/mochi/pkg/conf" + "github.com/sot-tech/mochi/pkg/log" + "github.com/sot-tech/mochi/pkg/metrics" + "github.com/sot-tech/mochi/pkg/stop" + "github.com/sot-tech/mochi/pkg/timecache" + "github.com/sot-tech/mochi/storage" +) + +const ( + // Name is the name by which this peer store is registered with Conf. + Name = "pg" + + defaultPingQuery = "SELECT 0" + + errRequiredParameterNotSetMsg = "required parameter not provided: %s" + errRequiredColumnsNotFoundMsg = "one or more required columns not found in result set: %v" + errRollBackMsg = "error occurred while rolling back failed query: %v, failed query error: %v" +) + +var ( + logger = log.NewLogger(Name) + + errConnectionStringNotProvided = errors.New("database connection string not provided") +) + +func init() { + // Register the storage builder. + storage.RegisterBuilder(Name, builder) +} + +func builder(icfg conf.MapConfig) (storage.PeerStorage, error) { + var cfg Config + + if err := icfg.Unmarshal(&cfg); err != nil { + return nil, err + } + + return newStore(cfg) +} + +func newStore(cfg Config) (storage.PeerStorage, error) { + cfg, err := cfg.Validate() + if err != nil { + return nil, err + } + + pgConf, err := pgxpool.ParseConfig(cfg.ConnectionString) + if err != nil { + return nil, err + } + + pgConf.ConnConfig.Logger = zerologadapter.NewLogger(logger.Logger) + con, err := pgxpool.Connect(context.Background(), cfg.ConnectionString) + if err != nil { + return nil, err + } + + return &store{Config: cfg, Pool: con, wg: sync.WaitGroup{}, closed: make(chan any)}, nil +} + +type peerQueryConf struct { + AddQuery string `cfg:"add_query"` + DelQuery string `cfg:"del_query"` + GraduateQuery string `cfg:"graduate_query"` + CountQuery string `cfg:"count_query"` + CountSeedersColumn string `cfg:"count_seeders_column"` + CountLeechersColumn string `cfg:"count_leechers_column"` + ByInfoHashClause string `cfg:"by_info_hash_clause"` +} + +type announceQueryConf struct { + Query string + PeerIDColumn string `cfg:"peer_id_column"` + AddressColumn string `cfg:"address_column"` + PortColumn string `cfg:"port_column"` +} + +type dataQueryConf struct { + AddQuery string `cfg:"add_query"` + GetQuery string `cfg:"get_query"` + DelQuery string `cfg:"del_query"` +} + +// Config holds the configuration of a redis PeerStorage. +type Config struct { + ConnectionString string `cfg:"connection_string"` + PingQuery string `cfg:"ping_query"` + Peer peerQueryConf + Announce announceQueryConf + Data dataQueryConf + GCQuery string `cfg:"gc_query"` + InfoHashCountQuery string `cfg:"info_hash_count_query"` +} + +// MarshalZerologObject writes configuration fields into zerolog event +func (cfg Config) MarshalZerologObject(e *zerolog.Event) { + e.Str("connectionString", ""). + Str("pingQuery", cfg.PingQuery). + Str("peer.addQuery", cfg.Peer.AddQuery). + Str("peer.delQuery", cfg.Peer.DelQuery). + Str("peer.graduateQuery", cfg.Peer.GraduateQuery). + Str("peer.countQuery", cfg.Peer.CountQuery). + Str("peer.countSeedersColumn", cfg.Peer.CountSeedersColumn). + Str("peer.countLeechersColumn", cfg.Peer.CountLeechersColumn). + Str("peer.byInfoHashClause", cfg.Peer.ByInfoHashClause). + Str("announce.query", cfg.Announce.Query). + Str("announce.peerIDColumn", cfg.Announce.PeerIDColumn). + Str("announce.addressColumn", cfg.Announce.AddressColumn). + Str("announce.portColumn", cfg.Announce.PortColumn). + Str("data.addQuery", cfg.Data.AddQuery). + Str("data.getQuery", cfg.Data.GetQuery). + Str("data.delQuery", cfg.Data.DelQuery). + Str("gcQuery", cfg.GCQuery). + Str("infoHashCountQuery", cfg.InfoHashCountQuery) +} + +// Validate sanity checks values set in a config and returns a new config with +// default values replacing anything that is invalid. +// +// This function warns to the logger when a value is changed. +func (cfg Config) Validate() (Config, error) { + validCfg := cfg + validCfg.ConnectionString = strings.TrimSpace(validCfg.ConnectionString) + if len(validCfg.ConnectionString) == 0 { + return cfg, errConnectionStringNotProvided + } + + if len(cfg.PingQuery) == 0 { + validCfg.PingQuery = defaultPingQuery + logger.Warn(). + Str("name", "PingQuery"). + Str("provided", cfg.PingQuery). + Str("default", validCfg.PingQuery). + Msg("falling back to default configuration") + } + + fn := func(p *string, name string) (err error) { + if *p = strings.TrimSpace(*p); len(*p) == 0 { + err = fmt.Errorf(errRequiredParameterNotSetMsg, name) + } + return + } + + if err := fn(&validCfg.Peer.AddQuery, "peer.addQuery"); err != nil { + return cfg, err + } + + if err := fn(&validCfg.Peer.DelQuery, "peer.aelQuery"); err != nil { + return cfg, err + } + + if err := fn(&validCfg.Peer.GraduateQuery, "peer.graduateQuery"); err != nil { + return cfg, err + } + + if err := fn(&validCfg.Peer.CountQuery, "peer.countQuery"); err != nil { + return cfg, err + } + + if err := fn(&validCfg.Peer.CountSeedersColumn, "peer.countSeedersColumn"); err != nil { + return cfg, err + } + + if err := fn(&validCfg.Peer.CountLeechersColumn, "peer.countLeechersColumn"); err != nil { + return cfg, err + } + + if err := fn(&validCfg.Peer.ByInfoHashClause, "peer.byInfoHashClause"); err != nil { + return cfg, err + } + + if err := fn(&validCfg.Announce.Query, "announce.query"); err != nil { + return cfg, err + } + + if err := fn(&validCfg.Announce.PeerIDColumn, "announce.peerIDColumn"); err != nil { + return cfg, err + } + + if err := fn(&validCfg.Announce.AddressColumn, "announce.addressColumn"); err != nil { + return cfg, err + } + + if err := fn(&validCfg.Announce.PortColumn, "announce.portColumn"); err != nil { + return cfg, err + } + + if err := fn(&validCfg.Data.AddQuery, "data.addQuery"); err != nil { + return cfg, err + } + + if err := fn(&validCfg.Data.GetQuery, "data.getQuery"); err != nil { + return cfg, err + } + + if err := fn(&validCfg.Data.DelQuery, "data.delQuery"); err != nil { + return cfg, err + } + + validCfg.Announce.PeerIDColumn = strings.ToUpper(validCfg.Announce.PeerIDColumn) + validCfg.Announce.AddressColumn = strings.ToUpper(validCfg.Announce.AddressColumn) + validCfg.Announce.PortColumn = strings.ToUpper(validCfg.Announce.PortColumn) + + validCfg.Peer.CountSeedersColumn = strings.ToUpper(validCfg.Peer.CountSeedersColumn) + validCfg.Peer.CountLeechersColumn = strings.ToUpper(validCfg.Peer.CountLeechersColumn) + + return validCfg, nil +} + +type store struct { + Config + *pgxpool.Pool + wg sync.WaitGroup + closed chan any +} + +func (s *store) Put(ctx string, values ...storage.Entry) (err error) { + var tx pgx.Tx + if tx, err = s.Begin(context.TODO()); err == nil { + for _, v := range values { + val := v.Value + switch tOut := val.(type) { + case string: + val = []byte(tOut) + } + if _, err = tx.Exec(context.TODO(), s.Data.AddQuery, ctx, []byte(v.Key), val); err != nil { + break + } + } + if err == nil { + err = tx.Commit(context.TODO()) + } else { + if txErr := tx.Rollback(context.TODO()); txErr != nil { + err = fmt.Errorf(errRollBackMsg, txErr, err) + } + } + } + return +} + +func (s *store) Contains(ctx string, key string) (contains bool, err error) { + var rows pgx.Rows + if rows, err = s.Query(context.TODO(), s.Data.GetQuery, ctx, []byte(key)); err == nil { + defer rows.Close() + contains = rows.Next() + } + return +} + +func (s *store) Load(ctx string, key string) (out any, err error) { + var rows pgx.Rows + if rows, err = s.Query(context.TODO(), s.Data.GetQuery, ctx, []byte(key)); err == nil { + defer rows.Close() + if rows.Next() { + var values []any + if values, err = rows.Values(); err == nil && len(values) > 0 { + out = values[0] + switch tOut := out.(type) { + case []byte: + out = string(tOut) + } + } + } + } + return +} + +func (s *store) Delete(ctx string, keys ...string) (err error) { + var tx pgx.Tx + if tx, err = s.Begin(context.TODO()); err == nil { + for _, k := range keys { + if _, err = tx.Exec(context.TODO(), s.Data.DelQuery, ctx, []byte(k)); err != nil { + break + } + } + if err == nil { + err = tx.Commit(context.TODO()) + } else { + if txErr := tx.Rollback(context.TODO()); txErr != nil { + err = fmt.Errorf(errRollBackMsg, txErr, err) + } + } + } + return +} + +func (s *store) Preservable() bool { + return true +} + +func (s *store) GCAware() bool { + return len(s.GCQuery) > 0 +} + +func (s *store) ScheduleGC(gcInterval, peerLifeTime time.Duration) { + s.wg.Add(1) + go func() { + defer s.wg.Done() + t := time.NewTimer(gcInterval) + defer t.Stop() + for { + select { + case <-s.closed: + return + case <-t.C: + start := time.Now() + _, err := s.Exec(context.Background(), s.GCQuery, time.Now().Add(-peerLifeTime)) + duration := time.Since(start) + if err != nil { + logger.Error().Err(err).Msg("error occurred while GC") + } else { + logger.Debug().Dur("timeTaken", duration).Msg("GC complete") + } + storage.PromGCDurationMilliseconds.Observe(float64(duration.Milliseconds())) + t.Reset(gcInterval) + } + } + }() +} + +func (s *store) StatisticsAware() bool { + return len(s.InfoHashCountQuery) > 0 +} + +func (s *store) ScheduleStatisticsCollection(reportInterval time.Duration) { + s.wg.Add(1) + go func() { + defer s.wg.Done() + t := time.NewTicker(reportInterval) + for { + select { + case <-s.closed: + t.Stop() + return + case <-t.C: + if metrics.Enabled() { + before := time.Now() + sc, lc := s.countPeers(bittorrent.NoneInfoHash) + var hc int + row := s.QueryRow(context.Background(), s.InfoHashCountQuery) + if err := row.Scan(&hc); err != nil && !errors.Is(err, pgx.ErrNoRows) { + logger.Error().Err(err).Msg("error occurred while get info hash count") + } + + storage.PromInfoHashesCount.Set(float64(hc)) + storage.PromSeedersCount.Set(float64(sc)) + storage.PromLeechersCount.Set(float64(lc)) + logger.Debug().TimeDiff("timeTaken", time.Now(), before).Msg("populate prom complete") + } + } + } + }() +} + +func (s *store) putPeer(ih bittorrent.InfoHash, peer bittorrent.Peer, seeder bool) error { + logger.Trace(). + Stringer("infoHash", ih). + Object("peer", peer). + Bool("seeder", seeder). + Msg("put peer") + args := []any{[]byte(ih), peer.ID[:], net.IP(peer.Addr().AsSlice()), peer.Port(), seeder, peer.Addr().Is6()} + if s.GCAware() { + args = append(args, timecache.Now()) + } + _, err := s.Exec(context.TODO(), s.Peer.AddQuery, args...) + return err +} + +func (s *store) delPeer(ih bittorrent.InfoHash, peer bittorrent.Peer, seeder bool) error { + logger.Trace(). + Stringer("infoHash", ih). + Object("peer", peer). + Msg("del peer") + _, err := s.Exec(context.TODO(), s.Peer.DelQuery, []byte(ih), peer.ID[:], net.IP(peer.Addr().AsSlice()), peer.Port(), seeder) + return err +} + +func (s *store) PutSeeder(ih bittorrent.InfoHash, peer bittorrent.Peer) error { + return s.putPeer(ih, peer, true) +} + +func (s *store) DeleteSeeder(ih bittorrent.InfoHash, peer bittorrent.Peer) error { + return s.delPeer(ih, peer, true) +} + +func (s *store) PutLeecher(ih bittorrent.InfoHash, peer bittorrent.Peer) error { + return s.putPeer(ih, peer, false) +} + +func (s *store) DeleteLeecher(ih bittorrent.InfoHash, peer bittorrent.Peer) error { + return s.delPeer(ih, peer, false) +} + +func (s *store) GraduateLeecher(ih bittorrent.InfoHash, peer bittorrent.Peer) error { + logger.Trace(). + Stringer("infoHash", ih). + Object("peer", peer). + Msg("graduate leecher") + _, err := s.Exec(context.TODO(), s.Peer.GraduateQuery, []byte(ih), peer.ID[:], net.IP(peer.Addr().AsSlice()), peer.Port()) + return err +} + +func (s *store) getPeers(ih bittorrent.InfoHash, seeders bool, maxCount int, isV6 bool) (peers []bittorrent.Peer, err error) { + var rows pgx.Rows + if rows, err = s.Query(context.TODO(), s.Announce.Query, []byte(ih), seeders, isV6, maxCount); err == nil { + defer rows.Close() + idIndex, ipIndex, portIndex := -1, -1, -1 + for i, field := range rows.FieldDescriptions() { + name := strings.ToUpper(string(field.Name)) + switch name { + case s.Announce.PeerIDColumn: + idIndex = i + case s.Announce.AddressColumn: + ipIndex = i + case s.Announce.PortColumn: + portIndex = i + } + } + if idIndex < 0 || ipIndex < 0 || portIndex < 0 { + err = fmt.Errorf(errRequiredColumnsNotFoundMsg, []string{s.Announce.PeerIDColumn, s.Announce.AddressColumn, s.Announce.PortColumn}) + return + } + var maxIndex int + switch { + case idIndex >= ipIndex && idIndex >= portIndex: + maxIndex = idIndex + case ipIndex >= idIndex && ipIndex >= portIndex: + maxIndex = ipIndex + case portIndex >= idIndex && portIndex >= ipIndex: + maxIndex = portIndex + } + + for rows.Next() && len(peers) < maxCount { + var peer bittorrent.Peer + var id []byte + var ip net.IP + var port int + into := make([]any, maxIndex+1) + into[idIndex], into[ipIndex], into[portIndex] = &id, &ip, &port + + if err = rows.Scan(into...); err == nil { + if peer.ID, err = bittorrent.NewPeerID(id); err == nil { + if netAddr, isOk := netip.AddrFromSlice(ip); isOk { + peer.AddrPort = netip.AddrPortFrom(netAddr, uint16(port)) + } else { + err = bittorrent.ErrInvalidIP + } + } + } + if err == nil { + peers = append(peers, peer) + } else { + logger.Warn(). + Err(err). + Bytes("peerID", id). + IPAddr("ip", ip). + Int("port", port). + Msg("unable to scan/construct peer") + } + } + } + return +} + +func (s *store) AnnouncePeers(ih bittorrent.InfoHash, forSeeder bool, numWant int, v6 bool) (peers []bittorrent.Peer, err error) { + logger.Trace(). + Stringer("infoHash", ih). + Bool("forSeeder", forSeeder). + Int("numWant", numWant). + Bool("v6", v6). + Msg("announce peers") + if forSeeder { + peers, err = s.getPeers(ih, false, numWant, v6) + } else { + if peers, err = s.getPeers(ih, true, numWant, v6); err == nil { + var addPeers []bittorrent.Peer + addPeers, err = s.getPeers(ih, false, numWant-len(peers), v6) + peers = append(peers, addPeers...) + } + } + + if l := len(peers); err == nil { + if l == 0 { + err = storage.ErrResourceDoesNotExist + } + } else if l > 0 { + err = nil + logger.Warn().Err(err).Stringer("infoHash", ih).Msg("error occurred while retrieving peers") + } + + return +} + +func (s *store) countPeers(ih bittorrent.InfoHash) (seeders int, leechers int) { + var rows pgx.Rows + var err error + if ih == bittorrent.NoneInfoHash { + rows, err = s.Query(context.TODO(), s.Peer.CountQuery) + } else { + rows, err = s.Query(context.TODO(), s.Peer.CountQuery+" "+s.Peer.ByInfoHashClause, []byte(ih)) + } + if err == nil { + defer rows.Close() + if rows.Next() { + si, li := -1, -1 + for i, field := range rows.FieldDescriptions() { + name := strings.ToUpper(string(field.Name)) + switch name { + case s.Peer.CountSeedersColumn: + si = i + case s.Peer.CountLeechersColumn: + li = i + } + } + if si < 0 || li < 0 { + err = fmt.Errorf(errRequiredColumnsNotFoundMsg, []string{s.Peer.CountSeedersColumn, s.Peer.CountLeechersColumn}) + } else { + var mi int + if si > li { + mi = si + } else { + mi = li + } + into := make([]any, mi+1) + into[si], into[li] = &seeders, &leechers + + err = rows.Scan(into...) + } + } + } + if err != nil { + logger.Error().Err(err).Stringer("infoHash", ih).Msg("unable to get peers count") + } + return +} + +func (s *store) ScrapeSwarm(ih bittorrent.InfoHash) (leechers uint32, seeders uint32, snatched uint32) { + logger.Trace(). + Stringer("infoHash", ih). + Msg("scrape swarm") + sc, lc := s.countPeers(ih) + seeders, leechers = uint32(sc), uint32(lc) + return +} + +func (s *store) Ping() error { + _, err := s.Exec(context.TODO(), s.PingQuery) + return err +} + +func (s *store) Stop() stop.Result { + c := make(stop.Channel) + go func() { + if s.closed != nil { + close(s.closed) + } + s.wg.Wait() + if s.Pool != nil { + logger.Info().Msg("pg exiting. mochi does not clear data in database when exiting.") + s.Close() + s.Pool = nil + } + c.Done() + }() + return c.Result() +} + +func (s *store) MarshalZerologObject(e *zerolog.Event) { + e.Str("type", Name).Object("config", s.Config) +} diff --git a/storage/pg/storage_test.go b/storage/pg/storage_test.go new file mode 100644 index 0000000..d48adab --- /dev/null +++ b/storage/pg/storage_test.go @@ -0,0 +1,82 @@ +package pg + +import ( + "context" + "fmt" + "testing" + + s "github.com/sot-tech/mochi/storage" + "github.com/sot-tech/mochi/storage/test" +) + +const ( + createTablesQuery = ` +DROP TABLE IF EXISTS mo_peers; +CREATE TABLE mo_peers ( + info_hash bytea NOT NULL, + peer_id bytea NOT NULL, + address inet NOT NULL, + port int4 NOT NULL, + is_seeder bool NOT NULL, + is_v6 bool NOT NULL, + created timestamp NOT NULL DEFAULT current_timestamp, + PRIMARY KEY (info_hash, peer_id, address, port) +); + +CREATE INDEX mo_peers_created_idx ON mo_peers(created); +CREATE INDEX mo_peers_announce_idx ON mo_peers(info_hash, is_seeder, is_v6); + +DROP TABLE IF EXISTS mo_kv; +CREATE TABLE mo_kv ( + context varchar NOT NULL, + name bytea NOT NULL, + value bytea, + PRIMARY KEY (context, name) +); +` +) + +var cfg = Config{ + ConnectionString: "host=127.0.0.1 database=test user=postgres pool_max_conns=50", + PingQuery: "SELECT 1", + Peer: peerQueryConf{ + AddQuery: "INSERT INTO mo_peers VALUES($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (info_hash, peer_id, address, port) DO UPDATE SET created = EXCLUDED.created, is_seeder = EXCLUDED.is_seeder", + DelQuery: "DELETE FROM mo_peers WHERE info_hash=$1 AND peer_id=$2 AND address=$3 AND port=$4 AND is_seeder=$5", + GraduateQuery: "UPDATE mo_peers SET is_seeder=TRUE WHERE info_hash=$1 AND peer_id=$2 AND address=$3 AND port=$4 AND NOT is_seeder", + CountQuery: "SELECT COUNT(1) FILTER (WHERE is_seeder) AS seeders, COUNT(1) FILTER (WHERE NOT is_seeder) AS leechers FROM mo_peers", + CountSeedersColumn: "seeders", + CountLeechersColumn: "leechers", + ByInfoHashClause: "WHERE info_hash = $1", + }, + Announce: announceQueryConf{ + Query: "SELECT peer_id, address, port FROM mo_peers WHERE info_hash=$1 AND is_seeder=$2 AND is_v6=$3 LIMIT $4", + PeerIDColumn: "peer_id", + AddressColumn: "address", + PortColumn: "port", + }, + Data: dataQueryConf{ + AddQuery: "INSERT INTO mo_kv VALUES($1, $2, $3) ON CONFLICT (context, name) DO NOTHING", + GetQuery: "SELECT value FROM mo_kv WHERE context=$1 AND name=$2", + DelQuery: "DELETE FROM mo_kv WHERE context=$1 AND name=$2", + }, + GCQuery: "DELETE FROM mo_peers WHERE created > $1", + InfoHashCountQuery: "SELECT COUNT(DISTINCT info_hash) as info_hashes FROM mo_peers", +} + +func createNew() s.PeerStorage { + var ps s.PeerStorage + var err error + ps, err = newStore(cfg) + if err != nil { + panic(fmt.Sprint("Unable to create PostgreSQL connection: ", err, "\nThis driver needs real PostgreSQL instance")) + } + pss := ps.(*store) + if _, err = pss.Exec(context.Background(), createTablesQuery); err != nil { + panic(fmt.Sprint("Unable to create test PostgreSQL tables: ", err)) + } + return ps +} + +func TestStorage(t *testing.T) { test.RunTests(t, createNew()) } + +func BenchmarkStorage(b *testing.B) { test.RunBenchmarks(b, createNew) } diff --git a/storage/redis/storage.go b/storage/redis/storage.go index b1a9042..a77d9d4 100644 --- a/storage/redis/storage.go +++ b/storage/redis/storage.go @@ -67,8 +67,8 @@ const ( var ( logger = log.NewLogger(Name) - // ErrSentinelAndClusterChecked returned from initializer if both Config.Sentinel and Config.Cluster provided - ErrSentinelAndClusterChecked = errors.New("unable to use both cluster and sentinel mode") + // errSentinelAndClusterChecked returned from initializer if both Config.Sentinel and Config.Cluster provided + errSentinelAndClusterChecked = errors.New("unable to use both cluster and sentinel mode") ) func init() { @@ -141,7 +141,7 @@ func (cfg Config) MarshalZerologObject(e *zerolog.Event) { // This function warns to the logger when a value is changed. func (cfg Config) Validate() (Config, error) { if cfg.Sentinel && cfg.Cluster { - return cfg, ErrSentinelAndClusterChecked + return cfg, errSentinelAndClusterChecked } validCfg := cfg @@ -319,7 +319,7 @@ func (ps *store) count(key string, getLength bool) (n uint64) { } err = AsNil(err) if err != nil { - logger.Error().Err(err).Str("key", key).Msg("storage: Redis: GET/SCARD failure") + logger.Error().Err(err).Str("key", key).Msg("GET/SCARD failure") } return } @@ -513,15 +513,15 @@ func (ps *Connection) GetPeers(ih bittorrent.InfoHash, forSeeder bool, maxCount return } -func (ps *store) AnnouncePeers(ih bittorrent.InfoHash, seeder bool, numWant int, v6 bool) ([]bittorrent.Peer, error) { +func (ps *store) AnnouncePeers(ih bittorrent.InfoHash, forSeeder bool, numWant int, v6 bool) ([]bittorrent.Peer, error) { logger.Trace(). Stringer("infoHash", ih). - Bool("seeder", seeder). + Bool("forSeeder", forSeeder). Int("numWant", numWant). Bool("v6", v6). Msg("announce peers") - return ps.GetPeers(ih, seeder, numWant, v6, func(ctx context.Context, infoHashKey string, maxCount int) *redis.StringSliceCmd { + return ps.GetPeers(ih, forSeeder, numWant, v6, func(ctx context.Context, infoHashKey string, maxCount int) *redis.StringSliceCmd { return ps.HRandField(ctx, infoHashKey, maxCount, false) }) } @@ -625,6 +625,14 @@ func (Connection) Preservable() bool { return true } +func (*store) GCAware() bool { + return true +} + +func (*store) StatisticsAware() bool { + return true +} + // Ping sends `PING` request to Redis server func (ps *Connection) Ping() error { return ps.UniversalClient.Ping(context.TODO()).Err() @@ -785,7 +793,7 @@ func (ps *store) Stop() stop.Result { ps.wg.Wait() var err error if ps.UniversalClient != nil { - logger.Info().Msg("storage: Redis: exiting. mochi does not clear data in redis when exiting. mochi keys have prefix " + PrefixKey) + logger.Info().Msg("redis exiting. mochi does not clear data in redis when exiting. mochi keys have prefix " + PrefixKey) err = ps.UniversalClient.Close() ps.UniversalClient = nil } diff --git a/storage/redis/storage_test.go b/storage/redis/storage_test.go index 3e305d4..a8ea916 100644 --- a/storage/redis/storage_test.go +++ b/storage/redis/storage_test.go @@ -11,7 +11,6 @@ import ( var cfg = Config{ Addresses: []string{"localhost:6379"}, - PeerLifetime: 30 * time.Minute, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, ConnectTimeout: 10 * time.Second, @@ -22,7 +21,7 @@ func createNew() s.PeerStorage { var err error ps, err = newStore(cfg) if err != nil { - panic(fmt.Sprint("Unable to create KeyDB connection: ", err, "\nThis driver needs real Redis instance")) + panic(fmt.Sprint("Unable to create Redis connection: ", err, "\nThis driver needs real Redis instance")) } return ps } diff --git a/storage/storage.go b/storage/storage.go index 89be535..65eba21 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -115,23 +115,6 @@ type DataStorage interface { Preservable() bool } -// GCAware is the interface for storage that supports periodic -// stale peers collection -type GCAware interface { - // ScheduleGC used to delete stale data, such as timed out seeders/leechers. - // Note: implementation must create subroutine by itself - ScheduleGC(gcInterval, peerLifeTime time.Duration) -} - -// StatisticsAware is the interface for storage that supports periodic -// statistics collection -type StatisticsAware interface { - // ScheduleStatisticsCollection used to receive statistics information about hashes, - // seeders and leechers count. - // Note: implementation must create subroutine by itself - ScheduleStatisticsCollection(reportInterval time.Duration) -} - // PeerStorage is an interface that abstracts the interactions of storing and // manipulating Peers such that it can be implemented for various data stores. // @@ -194,7 +177,7 @@ type PeerStorage interface { // leechers // // Returns ErrResourceDoesNotExist if the provided InfoHash is not tracked. - AnnouncePeers(ih bittorrent.InfoHash, seeder bool, numWant int, v6 bool) (peers []bittorrent.Peer, err error) + AnnouncePeers(ih bittorrent.InfoHash, forSeeder bool, numWant int, v6 bool) (peers []bittorrent.Peer, err error) // ScrapeSwarm returns information required to answer a Scrape request // about a Swarm identified by the given InfoHash. @@ -210,6 +193,23 @@ type PeerStorage interface { // (connection could be established, enough space etc.) Ping() error + // GCAware marks that this storage supports periodic + // peers collection + GCAware() bool + + // ScheduleGC used to delete stale data, such as timed out seeders/leechers. + // Note: implementation must create subroutine by itself + ScheduleGC(gcInterval, peerLifeTime time.Duration) + + // StatisticsAware marks that this storage supports periodic + // statistics collection + StatisticsAware() bool + + // ScheduleStatisticsCollection used to receive statistics information about hashes, + // seeders and leechers count. + // Note: implementation must create subroutine by itself + ScheduleStatisticsCollection(reportInterval time.Duration) + // Stopper is an interface that expects a Stop method to stop the PeerStorage. // For more details see the documentation in the stop package. stop.Stopper @@ -264,27 +264,27 @@ func NewStorage(name string, cfg conf.MapConfig) (ps PeerStorage, err error) { return } - if gc, isOk := ps.(GCAware); isOk { + if gc := ps.GCAware(); gc { gcInterval, peerTTL := c.sanitizeGCConfig() logger.Info(). Str("type", name). Dur("gcInterval", gcInterval). Dur("peerTTL", peerTTL). Msg("scheduling GC") - gc.ScheduleGC(gcInterval, peerTTL) + ps.ScheduleGC(gcInterval, peerTTL) } else { logger.Debug(). Str("type", name). Msg("storage does not support GC") } - if st, isOk := ps.(StatisticsAware); isOk { + if st := ps.StatisticsAware(); st { if statInterval := c.sanitizeStatisticsConfig(); statInterval > 0 { logger.Info(). Str("type", name). Dur("statInterval", statInterval). Msg("scheduling statistics collection") - st.ScheduleStatisticsCollection(statInterval) + ps.ScheduleStatisticsCollection(statInterval) } else { logger.Info().Str("type", name).Msg("statistics collection disabled because of zero reporting interval") } diff --git a/storage/test/storage_bench.go b/storage/test/storage_bench.go index 53b57c8..d822cc7 100644 --- a/storage/test/storage_bench.go +++ b/storage/test/storage_bench.go @@ -36,7 +36,7 @@ func generateInfoHashes() (a [ihCount]bittorrent.InfoHash) { func generatePeers() (a [peersCount]bittorrent.Peer) { for i := range a { var ip []byte - if i < peersCount/2 { + if i%2 == 0 { ip = make([]byte, net.IPv4len) } else { ip = make([]byte, net.IPv6len) diff --git a/storage/test/storage_test_base.go b/storage/test/storage_test_base.go index ae1495e..8c82890 100644 --- a/storage/test/storage_test_base.go +++ b/storage/test/storage_test_base.go @@ -13,6 +13,8 @@ import ( "github.com/sot-tech/mochi/storage" ) +const kvStoreCtx = "test" + func init() { _ = log.ConfigureLogger("", "warn", false, false) } @@ -39,7 +41,10 @@ type hashPeer struct { func (th *testHolder) DeleteSeeder(t *testing.T) { for _, c := range testData { err := th.st.DeleteSeeder(c.ih, c.peer) - require.Equal(t, storage.ErrResourceDoesNotExist, err) + if errors.Is(err, storage.ErrResourceDoesNotExist) { + err = nil + } + require.Nil(t, err) } } @@ -57,7 +62,10 @@ func (th *testHolder) PutLeecher(t *testing.T) { func (th *testHolder) DeleteLeecher(t *testing.T) { for _, c := range testData { err := th.st.DeleteLeecher(c.ih, c.peer) - require.Equal(t, storage.ErrResourceDoesNotExist, err) + if errors.Is(err, storage.ErrResourceDoesNotExist) { + err = nil + } + require.Nil(t, err) } } @@ -158,7 +166,10 @@ func (th *testHolder) LeecherPutGraduateAnnounceDeleteAnnounce(t *testing.T) { // Deleting the Peer as a Leecher should have no effect err = th.st.DeleteLeecher(c.ih, c.peer) - require.Equal(t, storage.ErrResourceDoesNotExist, err) + if errors.Is(err, storage.ErrResourceDoesNotExist) { + err = nil + } + require.Nil(t, err) // Verify it's still there peers, err = th.st.AnnouncePeers(c.ih, false, 50, isV6) @@ -171,23 +182,29 @@ func (th *testHolder) LeecherPutGraduateAnnounceDeleteAnnounce(t *testing.T) { // Test ErrDNE for missing leecher err = th.st.DeleteLeecher(c.ih, peer) - require.Equal(t, storage.ErrResourceDoesNotExist, err) + if errors.Is(err, storage.ErrResourceDoesNotExist) { + err = nil + } + require.Nil(t, err) err = th.st.DeleteSeeder(c.ih, c.peer) require.Nil(t, err) err = th.st.DeleteSeeder(c.ih, c.peer) - require.Equal(t, storage.ErrResourceDoesNotExist, err) + if errors.Is(err, storage.ErrResourceDoesNotExist) { + err = nil + } + require.Nil(t, err) } } func (th *testHolder) CustomPutContainsLoadDelete(t *testing.T) { for _, c := range testData { - err := th.st.Put("test", storage.Entry{Key: c.peer.String(), Value: c.ih.RawString()}) + err := th.st.Put(kvStoreCtx, storage.Entry{Key: c.peer.String(), Value: c.ih.RawString()}) require.Nil(t, err) // check if exist in ctx we put - contains, err := th.st.Contains("test", c.peer.String()) + contains, err := th.st.Contains(kvStoreCtx, c.peer.String()) require.Nil(t, err) require.True(t, contains) @@ -197,7 +214,7 @@ func (th *testHolder) CustomPutContainsLoadDelete(t *testing.T) { require.False(t, contains) // check value and type in ctx we put - out, err := th.st.Load("test", c.peer.String()) + out, err := th.st.Load(kvStoreCtx, c.peer.String()) require.Nil(t, err) ih, err := bittorrent.NewInfoHash(out) require.Nil(t, err) @@ -208,10 +225,10 @@ func (th *testHolder) CustomPutContainsLoadDelete(t *testing.T) { require.Nil(t, err) require.Nil(t, dummy) - err = th.st.Delete("test", c.peer.String()) + err = th.st.Delete(kvStoreCtx, c.peer.String()) require.Nil(t, err) - contains, err = th.st.Contains("peers", c.peer.String()) + contains, err = th.st.Contains("", c.peer.String()) require.Nil(t, err) require.False(t, contains) } @@ -228,29 +245,29 @@ func (th *testHolder) CustomBulkPutContainsLoadDelete(t *testing.T) { Value: c.ih.RawString(), }) } - err := th.st.Put("test", pairs...) + err := th.st.Put(kvStoreCtx, pairs...) require.Nil(t, err) // check if exist in ctx we put for _, k := range keys { - contains, err := th.st.Contains("test", k) + contains, err := th.st.Contains(kvStoreCtx, k) require.Nil(t, err) require.True(t, contains) } // check value and type in ctx we put for _, p := range pairs { - out, _ := th.st.Load("test", p.Key) + out, _ := th.st.Load(kvStoreCtx, p.Key) ih, err := bittorrent.NewInfoHash(out) require.Nil(t, err) require.Equal(t, p.Value, ih.RawString()) } - err = th.st.Delete("test", keys...) + err = th.st.Delete(kvStoreCtx, keys...) require.Nil(t, err) for _, k := range keys { - contains, err := th.st.Contains("test", k) + contains, err := th.st.Contains(kvStoreCtx, k) require.Nil(t, err) require.False(t, contains) }