From de6747fc0f2ec5291cd81ea5898299757f72c0b3 Mon Sep 17 00:00:00 2001 From: Alejandro Lopez Date: Fri, 31 Mar 2023 13:30:33 +0300 Subject: [PATCH] [#14] Add s3 local loader Signed-off-by: Alejandro Lopez --- README.md | 26 ++- examples/s3local.js | 15 ++ frostfs.go | 1 + go.mod | 49 ++-- go.sum | 99 ++++++-- internal/local/client.go | 116 ++-------- internal/local/local.go | 71 ++++-- internal/local/rawclient/option.go | 46 ++++ internal/local/rawclient/rawclient.go | 120 ++++++++++ internal/s3local/client.go | 120 ++++++++++ internal/s3local/frostfs.go | 90 ++++++++ internal/s3local/local.go | 166 ++++++++++++++ internal/s3local/resolver.go | 32 +++ internal/s3local/treeService.go | 211 ++++++++++++++++++ scenarios/preset/helpers/aws_cli.py | 12 +- .../preset/resolve_containers_in_preset.py | 29 +++ scenarios/run_scenarios.md | 25 ++- scenarios/s3local.js | 132 +++++++++++ 18 files changed, 1206 insertions(+), 154 deletions(-) create mode 100644 examples/s3local.js create mode 100644 internal/local/rawclient/option.go create mode 100644 internal/local/rawclient/rawclient.go create mode 100644 internal/s3local/client.go create mode 100644 internal/s3local/frostfs.go create mode 100644 internal/s3local/local.go create mode 100644 internal/s3local/resolver.go create mode 100644 internal/s3local/treeService.go create mode 100755 scenarios/preset/resolve_containers_in_preset.py create mode 100644 scenarios/s3local.js diff --git a/README.md b/README.md index 8ba7803..df62f95 100644 --- a/README.md +++ b/README.md @@ -117,9 +117,33 @@ const s3_cli = s3.connect("http://s3.frostfs.devenv:8080", {'no_verify_ssl': 'tr - `get(bucket, key)`. Returns dictionary with `success` boolean flag and `error` string. +## S3 Local + +Create local s3 client with `connect` method. Arguments: +- local path to frostfs storage node configuration file +- parameter map with the following options: + * `hex_key`: private key to use as a hexadecimal string. A random one is created if none is provided. + * `node_position`: position of this node in the node array if loading multiple nodes independently (default: 0). + * `node_count`: number of nodes in the node array if loading multiple nodes independently (default: 1). +- bucket-container mapping, which is needed to resolve the container id for a given bucket name. Any bucket + used by the client must have an entry here. + +```js +import local from 'k6/x/frostfs/local'; +const params = {'node_position': 1, 'node_count': 3} +const bucketMapping = {'mytestbucket': 'GBQDDUM1hdodXmiRHV57EUkFWJzuntsG8BG15wFSwam6'} +const local_client = local.connect("/path/to/config.yaml", params, bucketMapping) +``` + +### Methods +- `put(bucket, key, payload)`. Returns dictionary with `success` boolean flag + and `error` string. +- `get(bucket, key)`. Returns dictionary with `success` boolean flag and `error` + string. + # Examples -See native protocol and s3 test suit examples in [examples](./examples) dir. +See native protocol and s3 test suite examples in [examples](./examples) dir. # License diff --git a/examples/s3local.js b/examples/s3local.js new file mode 100644 index 0000000..830e510 --- /dev/null +++ b/examples/s3local.js @@ -0,0 +1,15 @@ +import {uuidv4} from 'https://jslib.k6.io/k6-utils/1.2.0/index.js'; +import s3local from 'k6/x/frostfs/s3local'; + +const bucket = "testbucket" +const payload = open('../go.sum', 'b'); +const s3local_cli = s3local.connect("path/to/storage/config.yml", {}, { + 'testbucket': 'GBQDDUM1hdodXmiRHV57EUkFWJzuntsG8BG15wFSwam6', +}); + +export default function () { + const key = uuidv4(); + if (s3local_cli.put(bucket, key, payload).success) { + s3local_cli.get(bucket, key) + } +} diff --git a/frostfs.go b/frostfs.go index f44aa55..a702e22 100644 --- a/frostfs.go +++ b/frostfs.go @@ -8,6 +8,7 @@ import ( _ "git.frostfs.info/TrueCloudLab/xk6-frostfs/internal/native" _ "git.frostfs.info/TrueCloudLab/xk6-frostfs/internal/registry" _ "git.frostfs.info/TrueCloudLab/xk6-frostfs/internal/s3" + _ "git.frostfs.info/TrueCloudLab/xk6-frostfs/internal/s3local" "go.k6.io/k6/js/modules" ) diff --git a/go.mod b/go.mod index 9d86de0..e7053cc 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,8 @@ go 1.17 require ( git.frostfs.info/TrueCloudLab/frostfs-node v0.22.2-0.20230313113918-4e244686cf03 - git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20230307124721-94476f905599 + git.frostfs.info/TrueCloudLab/frostfs-s3-gw v0.24.1-0.20230403110435-01afa1cae425 + git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20230329125804-552219b8e130 git.frostfs.info/TrueCloudLab/tzhash v1.8.0 github.com/aws/aws-sdk-go-v2 v1.16.3 github.com/aws/aws-sdk-go-v2/config v1.15.5 @@ -12,8 +13,8 @@ require ( github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf github.com/google/uuid v1.3.0 github.com/joho/godotenv v1.5.1 - github.com/nspcc-dev/neo-go v0.100.1 - github.com/panjf2000/ants/v2 v2.4.0 + github.com/nspcc-dev/neo-go v0.101.0 + github.com/panjf2000/ants/v2 v2.5.0 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.8.1 go.etcd.io/bbolt v1.3.6 @@ -22,11 +23,12 @@ require ( ) require ( - git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.11.2-0.20230307104236-f69d2ad83c51 // indirect + git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.11.2-0.20230315095236-9dc375346703 // indirect git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 // indirect git.frostfs.info/TrueCloudLab/hrw v1.2.0 // indirect git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 // indirect github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12 // indirect + github.com/aws/aws-sdk-go v1.44.6 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.12.0 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4 // indirect @@ -41,6 +43,9 @@ require ( github.com/aws/aws-sdk-go-v2/service/sso v1.11.4 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.16.4 // indirect github.com/aws/smithy-go v1.11.2 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bluele/gcache v0.0.2 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect @@ -48,43 +53,55 @@ require ( github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-sourcemap/sourcemap v2.1.4-0.20211119122758-180fcef48034+incompatible // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/gorilla/mux v1.8.0 // indirect github.com/hashicorp/golang-lru v0.6.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/klauspost/compress v1.15.13 // indirect - github.com/magiconair/properties v1.8.6 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // 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/minio/highwayhash v1.0.2 // indirect + github.com/minio/sio v0.3.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect + github.com/nats-io/jwt/v2 v2.4.1 // indirect + github.com/nats-io/nats.go v1.22.1 // indirect + github.com/nats-io/nkeys v0.4.4 // indirect + github.com/nats-io/nuid v1.0.1 // indirect github.com/nspcc-dev/rfc6979 v0.2.0 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect - github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.13.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/afero v1.9.2 // indirect + github.com/spf13/afero v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.14.0 // indirect - github.com/subosito/gotenv v1.4.1 // indirect + github.com/spf13/viper v1.15.0 // indirect + github.com/subosito/gotenv v1.4.2 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.4.0 // indirect + golang.org/x/crypto v0.7.0 // indirect golang.org/x/exp v0.0.0-20221227203929-1b447090c38c // indirect - golang.org/x/net v0.4.0 // indirect - golang.org/x/sys v0.3.0 // indirect - golang.org/x/text v0.5.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect golang.org/x/time v0.1.0 // indirect google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect - google.golang.org/grpc v1.51.0 // indirect + google.golang.org/grpc v1.52.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/guregu/null.v3 v3.3.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 81b9ea6..d59b322 100644 --- a/go.sum +++ b/go.sum @@ -114,8 +114,10 @@ cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOt cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= @@ -381,15 +383,19 @@ cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoIS cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.11.2-0.20230307104236-f69d2ad83c51 h1:l4+K1hN+NuWNtlZZoV8yRRP3Uu7PifL05ukEqKcb0Ks= git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.11.2-0.20230307104236-f69d2ad83c51/go.mod h1:n0DxKYulu2Ar73R6OcNF34LiL/Xa+iDR7GZuaOChbLE= +git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.11.2-0.20230315095236-9dc375346703 h1:lxe0DtZq/uFZVZu9apx6OcIXCJskQBMd/GVeYGKA3wA= +git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.11.2-0.20230315095236-9dc375346703/go.mod h1:gRd5iE5A84viily6AcNBsSlTx2XgoWrwRDz7z0MayDQ= git.frostfs.info/TrueCloudLab/frostfs-contract v0.0.0-20230307110621-19a8ef2d02fb/go.mod h1:nkR5gaGeez3Zv2SE7aceP0YwxG2FzIB5cGKpQO2vV2o= git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 h1:FxqFDhQYYgpe41qsIHVOcdzSVCB8JNSfPG7Uk4r2oSk= git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0/go.mod h1:RUIKZATQLJ+TaYQa60X2fTDwfuhMfm8Ar60bQ5fr+vU= git.frostfs.info/TrueCloudLab/frostfs-node v0.22.2-0.20230313113918-4e244686cf03 h1:OGLkpWNSIKEfpMEWHyK/iuid4NTipPo6xfXo1hhM2o8= git.frostfs.info/TrueCloudLab/frostfs-node v0.22.2-0.20230313113918-4e244686cf03/go.mod h1:1aLnRjy4a9XjQY1oCQ98roMTLQetPAdpbPHvrR978PU= -git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20230307124721-94476f905599 h1:mzGX2RX8R8H/tUqrUu1TcYk4QRDBcBIWGYscPncfLOQ= +git.frostfs.info/TrueCloudLab/frostfs-s3-gw v0.24.1-0.20230403110435-01afa1cae425 h1:vHDmz5CLJrw0JZR85TP57WqvjwgfTmbgOp/SQcmjUUg= +git.frostfs.info/TrueCloudLab/frostfs-s3-gw v0.24.1-0.20230403110435-01afa1cae425/go.mod h1:b0Z8M58N+uyOvfSWPO3ZWsqK1t9o/w2qj78ITNiUTOw= git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20230307124721-94476f905599/go.mod h1:z7zcpGY+puI5puyy5oyFbf20vWp84WtslCxcr6/kv5c= +git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20230329125804-552219b8e130 h1:V+3dGwEXwEvvSvseMKn8S6ZEMNhxBBYrcyx+F7VaptM= +git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20230329125804-552219b8e130/go.mod h1:23fUGlEv/ImaOi3vck6vZj0v0b4hteOhLLPnVWHSQeA= git.frostfs.info/TrueCloudLab/hrw v1.2.0 h1:KvAES7xIqmQBGd2q8KanNosD9+4BhU/zqD5Kt5KSflk= git.frostfs.info/TrueCloudLab/hrw v1.2.0/go.mod h1:mq2sbvYfO+BB6iFZwYBkgC0yc6mJNx+qZi4jW918m+Y= git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 h1:M2KR3iBj7WpY3hP10IevfIB9MURr4O9mwVfJ+SjT3HA= @@ -434,6 +440,8 @@ github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go v1.44.6 h1:Y+uHxmZfhRTLX2X3khkdxCoTZAyGEX21aOUHe1U6geg= +github.com/aws/aws-sdk-go v1.44.6/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go-v2 v1.16.3 h1:0W1TSJ7O6OzwuEvIXAtJGvOeQ0SGAhcpxPN2/NK5EhM= github.com/aws/aws-sdk-go-v2 v1.16.3/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 h1:SdK4Ppk5IzLs64ZMvr6MrSficMtjY2oS0WOORXTlxwU= @@ -472,8 +480,11 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj 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= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= +github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= @@ -488,9 +499,11 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= 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/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -660,6 +673,7 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -672,13 +686,17 @@ github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMd github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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/hashicorp/consul/api v1.15.3/go.mod h1:/g/qgcoBcEXALCNZgRRisyTW0nY86++L0KbeAMXYCeY= +github.com/hashicorp/consul/api v1.18.0/go.mod h1:owRRGJ9M5xReDC5nfT8FTJrNAPbT4NM6p/k+d03q2v4= github.com/hashicorp/consul/sdk v0.11.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= +github.com/hashicorp/consul/sdk v0.13.0/go.mod h1:0hs/l5fOVhJy/VdcoaNqUSi2AUs95eF5WKtv+EYIQqE= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -702,6 +720,7 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= @@ -715,8 +734,10 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hashicorp/serf v0.9.8/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -733,7 +754,9 @@ github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+ github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ= github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E= github.com/jhump/protoreflect v1.12.0/go.mod h1:JytZfP5d0r8pVNLZvai7U/MCuTWITgrI4tTg7puQFKI= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= @@ -754,6 +777,7 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexDMMOV0= @@ -774,8 +798,9 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -796,15 +821,20 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +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/mccutchen/go-httpbin v1.1.2-0.20190116014521-c5cb2f4802fa h1:lx8ZnNPwjkXSzOROz0cg69RlErRXs+L3eDkggASWKLo= github.com/mccutchen/go-httpbin v1.1.2-0.20190116014521-c5cb2f4802fa/go.mod h1:fhpOYavp5g2K74XDl/ao2y4KvhqVtKlkg1e+0UaQv7I= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= +github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/minio/sio v0.3.0 h1:syEFBewzOMOYVzSTFpp1MqpSZk8rUNbz8VIIc+PNzus= +github.com/minio/sio v0.3.0/go.mod h1:8b0yPp2avGThviy/+OCJBI6OMpvxoUuiLvE6F1lebhw= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -842,10 +872,19 @@ github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOEL 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/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= +github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4= +github.com/nats-io/jwt/v2 v2.4.1/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI= +github.com/nats-io/nats-server/v2 v2.7.1/go.mod h1:tckmrt0M6bVaDT3kmh9UrIq/CBOBBse+TpXQi5ldaa8= +github.com/nats-io/nats-server/v2 v2.7.4 h1:c+BZJ3rGzUKCBIM4IXO8uNT2u1vajGbD1kPA6wqCEaM= github.com/nats-io/nats-server/v2 v2.7.4/go.mod h1:1vZ2Nijh8tcyNe8BDVyTviCd9NYzRbubQYiEHsvOQWc= +github.com/nats-io/nats.go v1.13.1-0.20220121202836-972a071d373d/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= github.com/nats-io/nats.go v1.13.1-0.20220308171302-2f2f6968e98d/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nats.go v1.22.1 h1:XzfqDspY0RNufzdrB8c4hFR+R3dahkxlpWe5+IWJzbE= github.com/nats-io/nats.go v1.22.1/go.mod h1:tLqubohF7t4z3du1QDPYJIQQyhb4wl6DhjxEajSI7UA= github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= +github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA= +github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nspcc-dev/dbft v0.0.0-20191205084618-dacb1a30c254/go.mod h1:w1Ln2aT+dBlPhLnuZhBV+DfPEdS2CHWWLp5JTScY3bw= github.com/nspcc-dev/dbft v0.0.0-20191209120240-0d6b7568d9ae/go.mod h1:3FjXOoHmA51EGfb5GS/HOv7VdmngNRTssSeQ729dvGY= @@ -861,8 +900,9 @@ github.com/nspcc-dev/hrw v1.0.9/go.mod h1:l/W2vx83vMQo6aStyx2AuZrJ+07lGv2JQGlVkP github.com/nspcc-dev/neo-go v0.73.1-pre.0.20200303142215-f5a1b928ce09/go.mod h1:pPYwPZ2ks+uMnlRLUyXOpLieaDQSEaf4NM3zHVbRjmg= github.com/nspcc-dev/neo-go v0.98.0/go.mod h1:E3cc1x6RXSXrJb2nDWXTXjnXk3rIqVN8YdFyWv+FrqM= github.com/nspcc-dev/neo-go v0.99.4/go.mod h1:mKTolfRUfKjFso5HPvGSQtUZc70n0VKBMs16eGuC5gA= -github.com/nspcc-dev/neo-go v0.100.1 h1:yugxbQRdzM+ObVa5mtr9/n4rYjxSIrryne8MVr9NBwU= github.com/nspcc-dev/neo-go v0.100.1/go.mod h1:Nnp7F4e9IBccsgtCeLtUWV+0T6gk1PtP5HRtA13hUfc= +github.com/nspcc-dev/neo-go v0.101.0 h1:JPT2DpZqVjho34TMR59dm6uxvCFttOp02Nm8qCjpfaU= +github.com/nspcc-dev/neo-go v0.101.0/go.mod h1:Q0uWKivGc2mYgdKFmTNP49LeXwMu4x6pUzHm3OIsN2I= github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220927123257-24c107e3a262/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s= github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s= github.com/nspcc-dev/neofs-api-go/v2 v2.11.0-pre.0.20211201134523-3604d96f3fe1/go.mod h1:oS8dycEh8PPf2Jjp6+8dlwWyEv2Dy77h/XhhcdxYEFs= @@ -895,16 +935,18 @@ github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= -github.com/panjf2000/ants/v2 v2.4.0 h1:embKPQeNWMRbnrRKURv4TXJwjQRWMEAfqZT6Pe5hZNc= github.com/panjf2000/ants/v2 v2.4.0/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= +github.com/panjf2000/ants/v2 v2.5.0 h1:1rWGWSnxCsQBga+nQbA4/iY6VMeNoOIAM0ZWh9u3q2Q= +github.com/panjf2000/ants/v2 v2.5.0/go.mod h1:cU93usDlihJZ5CfRGNDYsiBYvoilLvBF5Qp/BT2GNRE= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/paulmach/orb v0.2.2/go.mod h1:FkcWtplUAIVqAuhAOV2d3rpbnQyliDOjOcLW9dUrfdU= github.com/paulmach/protoscan v0.2.1-0.20210522164731-4e53c6875432/go.mod h1:2sV+uZ/oQh66m4XJVZm5iqUZ62BN88Ex1E+TTS0nLzI= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -923,10 +965,12 @@ github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU= github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= 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= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= @@ -934,6 +978,7 @@ 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.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/prometheus/common v0.37.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.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -942,6 +987,7 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -952,6 +998,7 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.8.0/go.mod h1:TmKwZAo97S4Fy4sfMH/HX/cQP5D+ijra2NyLpNNmttY= +github.com/sagikazarmark/crypt v0.9.0/go.mod h1:RnH7sEhxfdnPm1z+XMgSLjWTEIjyK4z2dw6+4vHTMuo= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e h1:zWKUYT07mGmVBH+9UgnHXd/ekCK99C8EbDSAt5qsjXE= github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= @@ -965,8 +1012,9 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= +github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= @@ -975,8 +1023,9 @@ github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmq github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= +github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= +github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= 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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -992,8 +1041,9 @@ github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/syndtr/goleveldb v0.0.0-20180307113352-169b1b37be73/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -1003,6 +1053,7 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqri github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1017,9 +1068,13 @@ go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= +go.etcd.io/etcd/api/v3 v3.5.6/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= go.etcd.io/etcd/client/pkg/v3 v3.5.5/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ= +go.etcd.io/etcd/client/pkg/v3 v3.5.6/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ= go.etcd.io/etcd/client/v2 v2.305.5/go.mod h1:zQjKllfqfBVyVStbt4FaosoX2iYd8fV/GRy/PbowgP4= +go.etcd.io/etcd/client/v2 v2.305.6/go.mod h1:BHha8XJGe8vCIBfWBpbBLVZ4QjOIlfoouvOwydu63E0= go.etcd.io/etcd/client/v3 v3.5.5/go.mod h1:aApjR4WGlSumpnJ2kloS75h6aHUmAyaPLjHMxpc7E7c= +go.etcd.io/etcd/client/v3 v3.5.6/go.mod h1:f6GRinRMCsFVv9Ht42EyY7nfsVGwrNO0WEoS2pRKzQk= go.k6.io/k6 v0.38.2 h1:v4Dr7KhZVf+s6V6oz/pGtQ9ejTfNgUPcd/D3RH3GVdY= go.k6.io/k6 v0.38.2/go.mod h1:1bTdDsXTT2V3in3ZgdR15MDW6SQQh5nWni59tirqNB8= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -1054,6 +1109,7 @@ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnf 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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1074,8 +1130,10 @@ golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= 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= @@ -1118,6 +1176,7 @@ golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= @@ -1181,8 +1240,10 @@ golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1325,14 +1386,18 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/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-20210429154555-c04ba851c2a4/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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= @@ -1344,8 +1409,10 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1417,6 +1484,7 @@ golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +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= @@ -1477,6 +1545,7 @@ google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91 google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= 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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1594,6 +1663,7 @@ google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZV google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY= google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1632,8 +1702,9 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk= +google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= 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= diff --git a/internal/local/client.go b/internal/local/client.go index 146591f..88503a5 100644 --- a/internal/local/client.go +++ b/internal/local/client.go @@ -1,91 +1,39 @@ package local import ( - "crypto/ecdsa" "fmt" - "time" - "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" - "git.frostfs.info/TrueCloudLab/xk6-frostfs/internal/stats" + "git.frostfs.info/TrueCloudLab/xk6-frostfs/internal/local/rawclient" "github.com/dop251/goja" - "go.k6.io/k6/js/modules" - "go.k6.io/k6/metrics" ) type Client struct { - vu modules.VU - key ecdsa.PrivateKey - ng *engine.StorageEngine + rc *rawclient.RawClient } -type PutResponse struct { - Success bool - ObjectID string - Error string -} +type ( + SuccessOrErrorResponse struct { + Success bool + Error string + } -type GetResponse struct { - Success bool - Error string -} + PutResponse struct { + Success bool + ObjectID string + Error string + } -type DeleteResponse struct { - Success bool - Error string -} + GetResponse SuccessOrErrorResponse + DeleteResponse SuccessOrErrorResponse +) func (c *Client) Put(containerID string, headers map[string]string, payload goja.ArrayBuffer) PutResponse { - sz := len(payload.Bytes()) - - attrs := make([]object.Attribute, len(headers)) - { - ind := 0 - for k, v := range headers { - attrs[ind].SetKey(k) - attrs[ind].SetValue(v) - ind++ - } - } - - ownerID := &user.ID{} - user.IDFromKey(ownerID, c.key.PublicKey) - - obj := object.New() - obj.SetContainerID(mustParseContainerID(containerID)) - obj.SetOwnerID(ownerID) // needed for metabase bucket name - obj.SetAttributes(attrs...) - obj.SetPayload(payload.Bytes()) - obj.SetPayloadSize(uint64(len(payload.Bytes()))) - object.CalculateAndSetPayloadChecksum(obj) // needed for metabase key - - id, err := object.CalculateID(obj) + id, err := c.rc.Put(mustParseContainerID(containerID), nil, headers, payload.Bytes()) if err != nil { - return PutResponse{Error: fmt.Sprintf("calculating id: %v", err)} - } - obj.SetID(id) - - if err := object.CalculateAndSetSignature(c.key, obj); err != nil { - return PutResponse{Error: fmt.Sprintf("calculating signature: %v", err)} - } - - var req engine.PutPrm - req.WithObject(obj) - - start := time.Now() - - if _, err := c.ng.Put(req); err != nil { - stats.Report(c.vu, objPutFails, 1) return PutResponse{Error: err.Error()} } - - stats.Report(c.vu, objPutTotal, 1) - stats.ReportDataSent(c.vu, float64(sz)) - stats.Report(c.vu, objPutDuration, metrics.D(time.Since(start))) - return PutResponse{ Success: true, ObjectID: id.EncodeToString(), @@ -93,44 +41,16 @@ func (c *Client) Put(containerID string, headers map[string]string, payload goja } func (c *Client) Get(containerID, objectID string) GetResponse { - var addr oid.Address - addr.SetContainer(mustParseContainerID(containerID)) - addr.SetObject(mustParseObjectID(objectID)) - - var req engine.GetPrm - req.WithAddress(addr) - - start := time.Now() - resp, err := c.ng.Get(req) - if err != nil { - stats.Report(c.vu, objGetFails, 1) + if _, err := c.rc.Get(mustParseContainerID(containerID), mustParseObjectID(objectID)); err != nil { return GetResponse{Error: err.Error()} } - - stats.Report(c.vu, objGetTotal, 1) - stats.Report(c.vu, objGetDuration, metrics.D(time.Since(start))) - stats.ReportDataReceived(c.vu, float64(len(resp.Object().Payload()))) - return GetResponse{Success: true} } func (c *Client) Delete(containerID, objectID string) DeleteResponse { - var addr oid.Address - addr.SetContainer(mustParseContainerID(containerID)) - addr.SetObject(mustParseObjectID(objectID)) - - var req engine.DeletePrm - req.WithAddress(addr) - - start := time.Now() - if _, err := c.ng.Delete(req); err != nil { - stats.Report(c.vu, objDeleteFails, 1) + if err := c.rc.Delete(mustParseContainerID(containerID), mustParseObjectID(objectID)); err != nil { return DeleteResponse{Error: err.Error()} } - - stats.Report(c.vu, objDeleteTotal, 1) - stats.Report(c.vu, objDeleteDuration, metrics.D(time.Since(start))) - return DeleteResponse{Success: true} } diff --git a/internal/local/local.go b/internal/local/local.go index 96bf08d..88945c6 100644 --- a/internal/local/local.go +++ b/internal/local/local.go @@ -1,6 +1,7 @@ package local import ( + "context" "errors" "fmt" "sync" @@ -22,6 +23,8 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" + "git.frostfs.info/TrueCloudLab/xk6-frostfs/internal/local/rawclient" + "git.frostfs.info/TrueCloudLab/xk6-frostfs/internal/stats" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/panjf2000/ants/v2" "go.etcd.io/bbolt" @@ -44,7 +47,7 @@ type RootModule struct { // Local represents an instance of the module for every VU. type Local struct { vu modules.VU - resolveEngine func(string) (*engine.StorageEngine, error) + ResolveEngine func(context.Context, string) (*engine.StorageEngine, error) } // Ensure the interfaces are implemented correctly. @@ -64,17 +67,20 @@ func init() { // NewModuleInstance implements the modules.Module interface and returns // a new instance for each VU. func (r *RootModule) NewModuleInstance(vu modules.VU) modules.Instance { - mi := &Local{ - vu: vu, - resolveEngine: r.getOrCreateEngine, - } - return mi + return NewLocalModuleInstance(vu, r.GetOrCreateEngine) } -// getOrCreateEngine returns the current engine instance for the given configuration file, +func NewLocalModuleInstance(vu modules.VU, resolveEngine func(context.Context, string) (*engine.StorageEngine, error)) *Local { + return &Local{ + vu: vu, + ResolveEngine: resolveEngine, + } +} + +// GetOrCreateEngine returns the current engine instance for the given configuration file, // creating a new one if none exists. Note that the identity of configuration files is their // file name for the purposes of test runs. -func (r *RootModule) getOrCreateEngine(configFile string) (*engine.StorageEngine, error) { +func (r *RootModule) GetOrCreateEngine(ctx context.Context, configFile string) (*engine.StorageEngine, error) { r.mu.Lock() defer r.mu.Unlock() @@ -100,7 +106,7 @@ func (r *RootModule) getOrCreateEngine(configFile string) (*engine.StorageEngine return nil, fmt.Errorf("initializing engine: %v", err) } } else if configFile != r.configFile { - return nil, fmt.Errorf("getOrCreateEngine called with mismatching configFile after engine was initialized: got %q, want %q", configFile, r.configFile) + return nil, fmt.Errorf("GetOrCreateEngine called with mismatching configFile after engine was initialized: got %q, want %q", configFile, r.configFile) } return r.ng, nil @@ -112,18 +118,20 @@ func (s *Local) Exports() modules.Exports { return modules.Exports{Default: s} } +func (s *Local) VU() modules.VU { return s.vu } + func (s *Local) Connect(configFile, hexKey string) (*Client, error) { - ng, err := s.resolveEngine(configFile) + ng, err := s.ResolveEngine(s.VU().Context(), configFile) if err != nil { return nil, fmt.Errorf("connecting to engine for config %q: %v", configFile, err) } - key, err := parseOrCreateKey(hexKey) + key, err := ParseOrCreateKey(hexKey) if err != nil { return nil, fmt.Errorf("creating key: %v", err) } - // register metrics + // Register metrics. registry := metrics.NewRegistry() objPutTotal, _ = registry.NewMetric("local_obj_put_total", metrics.Counter) objPutFails, _ = registry.NewMetric("local_obj_put_fails", metrics.Counter) @@ -137,11 +145,37 @@ func (s *Local) Connect(configFile, hexKey string) (*Client, error) { objDeleteFails, _ = registry.NewMetric("local_obj_delete_fails", metrics.Counter) objDeleteDuration, _ = registry.NewMetric("local_obj_delete_duration", metrics.Trend, metrics.Time) - return &Client{ - vu: s.vu, - key: key.PrivateKey, - ng: ng, - }, nil + // Create raw client backed by local storage engine. + rc := rawclient.New(ng, + rawclient.WithKey(key.PrivateKey), + rawclient.WithPutHandler(func(sz uint64, err error, dt time.Duration) { + if err != nil { + stats.Report(s.vu, objPutFails, 1) + } else { + stats.Report(s.vu, objPutTotal, 1) + stats.ReportDataSent(s.vu, float64(sz)) + stats.Report(s.vu, objPutDuration, metrics.D(dt)) + } + }), + rawclient.WithGetHandler(func(sz uint64, err error, dt time.Duration) { + if err != nil { + stats.Report(s.vu, objGetFails, 1) + } else { + stats.Report(s.vu, objGetTotal, 1) + stats.Report(s.vu, objGetDuration, metrics.D(dt)) + stats.ReportDataReceived(s.vu, float64(sz)) + } + }), + rawclient.WithDeleteHandler(func(err error, dt time.Duration) { + if err != nil { + stats.Report(s.vu, objDeleteFails, 1) + } else { + stats.Report(s.vu, objDeleteTotal, 1) + stats.Report(s.vu, objDeleteDuration, metrics.D(dt)) + } + }), + ) + return &Client{rc}, nil } type epochState struct{} @@ -288,7 +322,8 @@ func storageEngineOptionsFromConfig(c *config.Config) ([]engine.Option, [][]shar return ngOpts, shOpts } -func parseOrCreateKey(hexKeyStr string) (*keys.PrivateKey, error) { +// ParseOrCreateKey parses the provided key as a hex string or creates a fresh one if empty. +func ParseOrCreateKey(hexKeyStr string) (*keys.PrivateKey, error) { if hexKeyStr != "" { return keys.NewPrivateKeyFromHex(hexKeyStr) } diff --git a/internal/local/rawclient/option.go b/internal/local/rawclient/option.go new file mode 100644 index 0000000..34b919a --- /dev/null +++ b/internal/local/rawclient/option.go @@ -0,0 +1,46 @@ +package rawclient + +import ( + "crypto/ecdsa" + "time" +) + +type ( + PutHandler func(uint64, error, time.Duration) + GetHandler func(uint64, error, time.Duration) + DeleteHandler func(error, time.Duration) +) + +type config struct { + key ecdsa.PrivateKey + onPut PutHandler + onGet GetHandler + onDelete DeleteHandler +} + +type Option func(*config) + +func defaultConfig() *config { + return &config{ + onPut: func(uint64, error, time.Duration) {}, + onGet: func(uint64, error, time.Duration) {}, + onDelete: func(error, time.Duration) {}, + } +} + +// WithKey sets the private key used by the raw client if no other key +// is available when setting owner IDs. +// Required. +func WithKey(key ecdsa.PrivateKey) Option { return func(c *config) { c.key = key } } + +// WithPutHandler sets the hook invoked on completion of Put calls. +// This is useful for updating metrics or debugging. +func WithPutHandler(h PutHandler) Option { return func(c *config) { c.onPut = h } } + +// WithGetHandler sets the hook invoked on completion of Get calls. +// This is useful for updating metrics or debugging. +func WithGetHandler(h GetHandler) Option { return func(c *config) { c.onGet = h } } + +// WithDeleteHandler sets the hook invoked on completion of Delete calls. +// This is useful for updating metrics or debugging. +func WithDeleteHandler(h DeleteHandler) Option { return func(c *config) { c.onDelete = h } } diff --git a/internal/local/rawclient/rawclient.go b/internal/local/rawclient/rawclient.go new file mode 100644 index 0000000..37960d5 --- /dev/null +++ b/internal/local/rawclient/rawclient.go @@ -0,0 +1,120 @@ +// Package rawclient provides a basic interface to the local storage engine. +// It can be used as a base for more complex load clients backed by local storage. +package rawclient + +import ( + "fmt" + "time" + + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" + oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" +) + +// RawClient is a client to the local storage engine instance. +type RawClient struct { + *config + ng *engine.StorageEngine + ownerID *user.ID +} + +// New returns a RawClient from the provided options. +func New(ng *engine.StorageEngine, opts ...Option) *RawClient { + cfg := defaultConfig() + for _, opt := range opts { + opt(cfg) + } + client := &RawClient{cfg, ng, &user.ID{}} + user.IDFromKey(client.ownerID, client.key.PublicKey) + return client +} + +func (c *RawClient) Put(containerID cid.ID, ownerID *user.ID, headers map[string]string, payload []byte) (oid.ID, error) { + sz := len(payload) + + attrs := make([]object.Attribute, len(headers)) + { + ind := 0 + for k, v := range headers { + attrs[ind].SetKey(k) + attrs[ind].SetValue(v) + ind++ + } + } + + // Note that the key is a required option, so this is never empty. + if ownerID == nil { + ownerID = c.ownerID + } + + obj := object.New() + obj.SetContainerID(containerID) + obj.SetOwnerID(ownerID) + obj.SetAttributes(attrs...) + obj.SetPayload(payload) + obj.SetPayloadSize(uint64(sz)) + object.CalculateAndSetPayloadChecksum(obj) // needed for metabase key + + id, err := object.CalculateID(obj) + if err != nil { + return oid.ID{}, fmt.Errorf("calculating object id: %v", err) + } + obj.SetID(id) + + if err := object.CalculateAndSetSignature(c.key, obj); err != nil { + return oid.ID{}, fmt.Errorf("calculating signature: %v", err) + } + + var req engine.PutPrm + req.WithObject(obj) + + start := time.Now() + _, err = c.ng.Put(req) + c.onPut(uint64(sz), err, time.Since(start)) + if err != nil { + return oid.ID{}, err + } + + return id, nil +} + +func (c *RawClient) Get(containerID cid.ID, objectID oid.ID) (*object.Object, error) { + var addr oid.Address + addr.SetContainer(containerID) + addr.SetObject(objectID) + + var req engine.GetPrm + req.WithAddress(addr) + + start := time.Now() + res, err := c.ng.Get(req) + + var sz uint64 + obj := res.Object() + if obj != nil { + sz = uint64(len(obj.Payload())) + } + + c.onGet(sz, err, time.Since(start)) + return obj, nil +} + +func (c *RawClient) Delete(containerID cid.ID, objectID oid.ID) error { + var addr oid.Address + addr.SetContainer(containerID) + addr.SetObject(objectID) + + var req engine.DeletePrm + req.WithAddress(addr) + + start := time.Now() + _, err := c.ng.Delete(req) + c.onDelete(err, time.Since(start)) + return err +} + +func (c *RawClient) OwnerID() *user.ID { + return c.ownerID +} diff --git a/internal/s3local/client.go b/internal/s3local/client.go new file mode 100644 index 0000000..c637d2c --- /dev/null +++ b/internal/s3local/client.go @@ -0,0 +1,120 @@ +package s3local + +import ( + "bytes" + "time" + + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" + "git.frostfs.info/TrueCloudLab/xk6-frostfs/internal/stats" + "github.com/dop251/goja" + "go.k6.io/k6/js/modules" + "go.k6.io/k6/metrics" +) + +type Client struct { + vu modules.VU + l layer.Client + ownerID *user.ID + resolver layer.BucketResolver +} + +type ( + SuccessOrErrorResponse struct { + Success bool + Error string + } + + CreateBucketResponse SuccessOrErrorResponse + PutResponse SuccessOrErrorResponse + DeleteResponse SuccessOrErrorResponse + GetResponse SuccessOrErrorResponse +) + +func (c *Client) Put(bucket, key string, payload goja.ArrayBuffer) PutResponse { + cid, err := c.resolver.Resolve(c.vu.Context(), bucket) + if err != nil { + stats.Report(c.vu, objPutFails, 1) + return PutResponse{Error: err.Error()} + } + + prm := &layer.PutObjectParams{ + BktInfo: &data.BucketInfo{ + Name: bucket, + CID: cid, + Owner: *c.ownerID, + Created: time.Now(), + }, + Header: map[string]string{}, + Object: key, + Size: int64(len(payload.Bytes())), + Reader: bytes.NewReader(payload.Bytes()), + } + + start := time.Now() + if _, err := c.l.PutObject(c.vu.Context(), prm); err != nil { + stats.Report(c.vu, objPutFails, 1) + return PutResponse{Error: err.Error()} + } + + stats.Report(c.vu, objPutDuration, metrics.D(time.Since(start))) + stats.Report(c.vu, objPutTotal, 1) + stats.ReportDataSent(c.vu, float64(prm.Size)) + + return PutResponse{Success: true} +} + +func (c *Client) Get(bucket, key string) GetResponse { + cid, err := c.resolver.Resolve(c.vu.Context(), bucket) + if err != nil { + stats.Report(c.vu, objGetFails, 1) + return GetResponse{Error: err.Error()} + } + + start := time.Now() + + bktInfo := &data.BucketInfo{ + Name: bucket, + CID: cid, + Owner: *c.ownerID, + } + + headPrm := &layer.HeadObjectParams{ + BktInfo: bktInfo, + Object: key, + } + extInfo, err := c.l.GetExtendedObjectInfo(c.vu.Context(), headPrm) + if err != nil { + stats.Report(c.vu, objGetFails, 1) + return GetResponse{Error: err.Error()} + } + + wr := &recvDataReporter{} + getPrm := &layer.GetObjectParams{ + BucketInfo: bktInfo, + ObjectInfo: extInfo.ObjectInfo, + Range: &layer.RangeParams{ + Start: 0, + End: uint64(extInfo.ObjectInfo.Size), + }, + Writer: wr, + } + if err := c.l.GetObject(c.vu.Context(), getPrm); err != nil { + stats.Report(c.vu, objGetFails, 1) + return GetResponse{Error: err.Error()} + } + + stats.Report(c.vu, objGetDuration, metrics.D(time.Since(start))) + stats.Report(c.vu, objGetTotal, 1) + stats.ReportDataReceived(c.vu, wr.total) + + return GetResponse{Success: true} +} + +type recvDataReporter struct{ total float64 } + +func (r *recvDataReporter) Write(p []byte) (int, error) { + r.total += float64(len(p)) + return len(p), nil +} diff --git a/internal/s3local/frostfs.go b/internal/s3local/frostfs.go new file mode 100644 index 0000000..d751bcf --- /dev/null +++ b/internal/s3local/frostfs.go @@ -0,0 +1,90 @@ +package s3local + +import ( + "bytes" + "context" + "fmt" + "io" + "time" + + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" + oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" + "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" + "git.frostfs.info/TrueCloudLab/xk6-frostfs/internal/local/rawclient" +) + +// frostfs implements the subset of layer.FrostFS needed for clients +// backed by local storage engines. Attempting to call any of the +// unimplemented methods panics. +type frostfs struct { + *rawclient.RawClient +} + +func unimplementedMessage(fname string) string { + return fmt.Sprintf("layer.FrostFS.%s is unimplemented and should not be called. If you are seeing "+ + "this error, it probably means you tried to use the s3local scenario for "+ + "something other than filling a cluster (i.e. PUT or GET).", fname) +} + +func (*frostfs) CreateContainer(context.Context, layer.PrmContainerCreate) (cid.ID, error) { + panic(unimplementedMessage("CreateContainer")) +} + +func (*frostfs) Container(context.Context, cid.ID) (*container.Container, error) { + panic(unimplementedMessage("Container")) +} + +func (*frostfs) UserContainers(context.Context, user.ID) ([]cid.ID, error) { + panic(unimplementedMessage("UserContainers")) +} + +func (*frostfs) SetContainerEACL(context.Context, eacl.Table, *session.Container) error { + panic(unimplementedMessage("SetContainerEACL")) +} + +func (*frostfs) ContainerEACL(context.Context, cid.ID) (*eacl.Table, error) { + panic(unimplementedMessage("ContainerEACL")) +} + +func (*frostfs) DeleteContainer(context.Context, cid.ID, *session.Container) error { + panic(unimplementedMessage("DeleteContainer")) +} + +func (f *frostfs) ReadObject(ctx context.Context, prm layer.PrmObjectRead) (*layer.ObjectPart, error) { + obj, err := f.Get(prm.Container, prm.Object) + if err != nil { + return nil, err + } + part := &layer.ObjectPart{} + if prm.WithHeader { + part.Head = obj + } + if prm.WithPayload { + part.Payload = io.NopCloser(bytes.NewReader(obj.Payload())) + } + return part, nil +} + +func (f *frostfs) CreateObject(ctx context.Context, prm layer.PrmObjectCreate) (oid.ID, error) { + payload, err := io.ReadAll(prm.Payload) + if err != nil { + return oid.ID{}, fmt.Errorf("reading payload: %v", err) + } + hdrs := map[string]string{} + for _, attr := range prm.Attributes { + hdrs[attr[0]] = attr[1] + } + return f.Put(prm.Container, &prm.Creator, hdrs, payload) +} + +func (f *frostfs) DeleteObject(context.Context, layer.PrmObjectDelete) error { + panic(unimplementedMessage("DeleteObject")) +} + +func (f *frostfs) TimeToEpoch(ctx context.Context, now time.Time, future time.Time) (uint64, uint64, error) { + panic(unimplementedMessage("TimeToEpoch")) +} diff --git a/internal/s3local/local.go b/internal/s3local/local.go new file mode 100644 index 0000000..f0ba436 --- /dev/null +++ b/internal/s3local/local.go @@ -0,0 +1,166 @@ +package s3local + +import ( + "context" + "flag" + "fmt" + "time" + + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer" + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/tree" + "git.frostfs.info/TrueCloudLab/xk6-frostfs/internal/local" + "git.frostfs.info/TrueCloudLab/xk6-frostfs/internal/local/rawclient" + "git.frostfs.info/TrueCloudLab/xk6-frostfs/internal/stats" + "go.k6.io/k6/js/modules" + "go.k6.io/k6/metrics" + "go.uber.org/zap" +) + +// RootModule is the global module object type. It is instantiated once per test +// run and will be used to create k6/x/frostfs/s3local module instances for each VU. +type RootModule struct { + m *local.RootModule +} + +// Local represents an instance of the module for every VU. +type Local struct { + l *local.Local +} + +// Ensure the interfaces are implemented correctly. +var ( + _ modules.Module = &RootModule{} + _ modules.Instance = &Local{} + + internalObjPutTotal, internalObjPutFails, internalObjPutDuration *metrics.Metric + internalObjGetTotal, internalObjGetFails, internalObjGetDuration *metrics.Metric + objPutTotal, objPutFails, objPutDuration *metrics.Metric + objGetTotal, objGetFails, objGetDuration *metrics.Metric +) + +func init() { + modules.Register("k6/x/frostfs/s3local", &RootModule{ + m: &local.RootModule{}, + }) +} + +// NewModuleInstance implements the modules.Module interface and returns +// a new instance for each VU. +func (r *RootModule) NewModuleInstance(vu modules.VU) modules.Instance { + return &Local{local.NewLocalModuleInstance(vu, r.m.GetOrCreateEngine)} +} + +// Exports implements the modules.Instance interface and returns the exports +// of the JS module. +func (s *Local) Exports() modules.Exports { + return modules.Exports{Default: s} +} + +func (s *Local) Connect(configFile string, params map[string]string, bucketMapping map[string]string) (*Client, error) { + // Parse configuration flags. + fs := flag.NewFlagSet("s3local", flag.ContinueOnError) + + hexKey := fs.String("hex_key", "", "Private key to use as a hexadecimal string. A random one is created if none is provided") + nodePosition := fs.Int("node_position", 0, "Position of this node in the node array if loading multiple nodes independently") + nodeCount := fs.Int("node_count", 1, "Number of nodes in the node array if loading multiple nodes independently") + + { + args := make([]string, 0, 2*len(params)) + for k, v := range params { + args = append(args, "-"+k, v) + } + if err := fs.Parse(args); err != nil { + return nil, fmt.Errorf("parsing parameters: %v", err) + } + } + + // Validate and read configuration flags. + key, err := local.ParseOrCreateKey(*hexKey) + if err != nil { + return nil, fmt.Errorf("parsing hex_key: %v", err) + } + if *nodeCount <= 0 { + return nil, fmt.Errorf("node_count must be positive") + } + if *nodePosition < 0 || *nodePosition >= *nodeCount { + return nil, fmt.Errorf("node_position must be in the range [0, node_count-1]") + } + + // Register metrics. + registry := metrics.NewRegistry() + + internalObjPutTotal, _ = registry.NewMetric("s3local_internal_obj_put_total", metrics.Counter) + internalObjPutFails, _ = registry.NewMetric("s3local_internal_obj_put_fails", metrics.Counter) + internalObjPutDuration, _ = registry.NewMetric("s3local_internal_obj_put_duration", metrics.Trend, metrics.Time) + + internalObjGetTotal, _ = registry.NewMetric("s3local_internal_obj_get_total", metrics.Counter) + internalObjGetFails, _ = registry.NewMetric("s3local_internal_obj_get_fails", metrics.Counter) + internalObjGetDuration, _ = registry.NewMetric("s3local_internal_obj_get_duration", metrics.Trend, metrics.Time) + + objPutTotal, _ = registry.NewMetric("s3local_obj_put_total", metrics.Counter) + objPutFails, _ = registry.NewMetric("s3local_obj_put_fails", metrics.Counter) + objPutDuration, _ = registry.NewMetric("s3local_obj_put_duration", metrics.Trend, metrics.Time) + + objGetTotal, _ = registry.NewMetric("s3local_obj_get_total", metrics.Counter) + objGetFails, _ = registry.NewMetric("s3local_obj_get_fails", metrics.Counter) + objGetDuration, _ = registry.NewMetric("s3local_obj_get_duration", metrics.Trend, metrics.Time) + + // Create S3 layer backed by local storage engine and tree service. + ng, err := s.l.ResolveEngine(s.l.VU().Context(), configFile) + if err != nil { + return nil, fmt.Errorf("connecting to engine for config %q: %v", configFile, err) + } + + treeSvc := tree.NewTree(treeServiceEngineWrapper{ + ng: ng, + pos: *nodePosition, + size: *nodeCount, + }) + + rc := rawclient.New(ng, + rawclient.WithKey(key.PrivateKey), + rawclient.WithPutHandler(func(sz uint64, err error, dt time.Duration) { + if err != nil { + stats.Report(s.l.VU(), internalObjPutFails, 1) + } else { + stats.Report(s.l.VU(), internalObjPutTotal, 1) + stats.Report(s.l.VU(), internalObjPutDuration, metrics.D(dt)) + } + }), + rawclient.WithGetHandler(func(sz uint64, err error, dt time.Duration) { + if err != nil { + stats.Report(s.l.VU(), internalObjGetFails, 1) + } else { + stats.Report(s.l.VU(), internalObjGetTotal, 1) + stats.Report(s.l.VU(), internalObjGetDuration, metrics.D(dt)) + } + }), + ) + + resolver, err := newFixedBucketResolver(bucketMapping) + if err != nil { + return nil, fmt.Errorf("creating bucket resolver: %v", err) + } + + cfg := &layer.Config{ + Caches: layer.DefaultCachesConfigs(zap.L()), + AnonKey: layer.AnonymousKey{Key: key}, + Resolver: resolver, + TreeService: treeSvc, + } + + l := layer.NewLayer(zap.L(), &frostfs{rc}, cfg) + l.Initialize(s.l.VU().Context(), nopEventListener{}) + + return &Client{ + vu: s.l.VU(), + l: l, + ownerID: rc.OwnerID(), + resolver: resolver, + }, nil +} + +type nopEventListener struct{} + +func (nopEventListener) Subscribe(context.Context, string, layer.MsgHandler) error { return nil } +func (nopEventListener) Listen(context.Context) {} diff --git a/internal/s3local/resolver.go b/internal/s3local/resolver.go new file mode 100644 index 0000000..48dbb7a --- /dev/null +++ b/internal/s3local/resolver.go @@ -0,0 +1,32 @@ +package s3local + +import ( + "context" + "fmt" + + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" +) + +// fixedBucketResolver is a static bucket resolver from the provided map. +// This is needed to replace the normal resolver for local storage engine clients, since +// those should not use DNS or NNS for resolution. +type fixedBucketResolver map[string]cid.ID + +func newFixedBucketResolver(bucketMapping map[string]string) (fixedBucketResolver, error) { + r := fixedBucketResolver{} + for bucket, cidStr := range bucketMapping { + var id cid.ID + if err := id.DecodeString(cidStr); err != nil { + return nil, fmt.Errorf("decoding container id %q: %v", cidStr, err) + } + r[bucket] = id + } + return r, nil +} + +func (r fixedBucketResolver) Resolve(_ context.Context, bucket string) (cid.ID, error) { + if cid, resolved := r[bucket]; resolved { + return cid, nil + } + return cid.ID{}, fmt.Errorf("bucket %s is not mapped to any container", bucket) +} diff --git a/internal/s3local/treeService.go b/internal/s3local/treeService.go new file mode 100644 index 0000000..0b41ade --- /dev/null +++ b/internal/s3local/treeService.go @@ -0,0 +1,211 @@ +package s3local + +import ( + "context" + "errors" + "fmt" + + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/pilorama" + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/tree" +) + +// treeServiceEngineWrapper implements the basic functioning of tree service using +// only the local storage engine instance. The node position and count is fixed +// beforehand in order to coordinate multiple runs on different nodes of the same +// cluster. +// +// The implementation mostly emulates the following +// +// - https://git.frostfs.info/TrueCloudLab/frostfs-node/src/branch/master/pkg/services/tree/service.go +// - https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/src/branch/master/internal/frostfs/services/tree_client_grpc.go +// +// but skips details which are irrelevant for local storage engine-backed clients. +type treeServiceEngineWrapper struct { + ng *engine.StorageEngine + pos int + size int +} + +type kv struct { + k string + v []byte +} + +func (kv kv) GetKey() string { return kv.k } +func (kv kv) GetValue() []byte { return kv.v } + +type nodeResponse struct { + meta []tree.Meta + nodeID uint64 + parentID uint64 + ts uint64 +} + +func (r nodeResponse) GetMeta() []tree.Meta { return r.meta } +func (r nodeResponse) GetNodeID() uint64 { return r.nodeID } +func (r nodeResponse) GetParentID() uint64 { return r.parentID } +func (r nodeResponse) GetTimestamp() uint64 { return r.ts } + +func (s treeServiceEngineWrapper) GetNodes(ctx context.Context, p *tree.GetNodesParams) ([]tree.NodeResponse, error) { + nodeIDs, err := s.ng.TreeGetByPath(p.BktInfo.CID, p.TreeID, pilorama.AttributeFilename, p.Path, p.LatestOnly) + if err != nil { + if errors.Is(err, pilorama.ErrTreeNotFound) { + // This is needed in order for the tree implementation to create the tree/node + // if it doesn't exist already. + // See: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/src/branch/master/internal/frostfs/services/tree_client_grpc.go#L306 + return nil, tree.ErrNodeNotFound + } + return nil, err + } + + resps := make([]tree.NodeResponse, 0, len(nodeIDs)) + for _, nodeID := range nodeIDs { + m, parentID, err := s.ng.TreeGetMeta(p.BktInfo.CID, p.TreeID, nodeID) + if err != nil { + return nil, err + } + resp := nodeResponse{ + parentID: parentID, + nodeID: nodeID, + ts: m.Time, + } + if p.AllAttrs { + resp.meta = kvToTreeMeta(m.Items) + } else { + for _, it := range m.Items { + for _, attr := range p.Meta { + if it.Key == attr { + resp.meta = append(resp.meta, kv{it.Key, it.Value}) + } + break + } + } + } + resps = append(resps, resp) + } + + return resps, nil +} + +func (s treeServiceEngineWrapper) GetSubTree(ctx context.Context, bktInfo *data.BucketInfo, treeID string, rootID uint64, depth uint32) ([]tree.NodeResponse, error) { + var resps []tree.NodeResponse + + var traverse func(nodeID uint64, curDepth uint32) error + traverse = func(nodeID uint64, curDepth uint32) error { + m, parentID, err := s.ng.TreeGetMeta(bktInfo.CID, treeID, nodeID) + if err != nil { + return fmt.Errorf("getting meta: %v", err) + } + + resps = append(resps, nodeResponse{ + nodeID: nodeID, + parentID: parentID, + ts: m.Time, + meta: kvToTreeMeta(m.Items), + }) + + if curDepth >= depth { + return nil + } + + children, err := s.ng.TreeGetChildren(bktInfo.CID, treeID, nodeID) + if err != nil { + return fmt.Errorf("getting children: %v", err) + } + for _, child := range children { + if err := traverse(child, curDepth+1); err != nil { + return err + } + } + return nil + } + + if err := traverse(rootID, 0); err != nil { + return nil, fmt.Errorf("traversing: %v", err) + } + + return resps, nil +} + +func (s treeServiceEngineWrapper) AddNode(ctx context.Context, bktInfo *data.BucketInfo, treeID string, parentID uint64, meta map[string]string) (uint64, error) { + desc := pilorama.CIDDescriptor{ + CID: bktInfo.CID, + Position: s.pos, + Size: s.size, + } + mv, err := s.ng.TreeMove(desc, treeID, &pilorama.Move{ + Parent: parentID, + Child: pilorama.RootID, + Meta: pilorama.Meta{Items: mapToKV(meta)}, + }) + return mv.Child, err +} + +func (s treeServiceEngineWrapper) AddNodeByPath(ctx context.Context, bktInfo *data.BucketInfo, treeID string, path []string, meta map[string]string) (uint64, error) { + desc := pilorama.CIDDescriptor{ + CID: bktInfo.CID, + Position: s.pos, + Size: s.size, + } + mvs, err := s.ng.TreeAddByPath(desc, treeID, pilorama.AttributeFilename, path, mapToKV(meta)) + if err != nil { + return pilorama.TrashID, err + } + return mvs[len(mvs)-1].Child, nil +} + +func (s treeServiceEngineWrapper) MoveNode(ctx context.Context, bktInfo *data.BucketInfo, treeID string, nodeID, parentID uint64, meta map[string]string) error { + if nodeID == pilorama.RootID { + return fmt.Errorf("node with ID %d is the root and can't be moved", nodeID) + } + desc := pilorama.CIDDescriptor{ + CID: bktInfo.CID, + Position: s.pos, + Size: s.size, + } + _, err := s.ng.TreeMove(desc, treeID, &pilorama.Move{ + Parent: parentID, + Child: nodeID, + Meta: pilorama.Meta{ + Items: mapToKV(meta), + }, + }) + return err +} + +func (s treeServiceEngineWrapper) RemoveNode(ctx context.Context, bktInfo *data.BucketInfo, treeID string, nodeID uint64) error { + if nodeID == pilorama.RootID { + return fmt.Errorf("node with ID %d is the root and can't be removed", nodeID) + } + desc := pilorama.CIDDescriptor{ + CID: bktInfo.CID, + Position: s.pos, + Size: s.size, + } + _, err := s.ng.TreeMove(desc, treeID, &pilorama.Move{ + Parent: pilorama.TrashID, + Child: nodeID, + }) + return err +} + +func mapToKV(m map[string]string) []pilorama.KeyValue { + var kvs []pilorama.KeyValue + for k, v := range m { + kvs = append(kvs, pilorama.KeyValue{ + Key: k, + Value: []byte(v), + }) + } + return kvs +} + +func kvToTreeMeta(x []pilorama.KeyValue) []tree.Meta { + ret := make([]tree.Meta, 0, len(x)) + for _, x := range x { + ret = append(ret, kv{x.Key, x.Value}) + } + return ret +} diff --git a/scenarios/preset/helpers/aws_cli.py b/scenarios/preset/helpers/aws_cli.py index 6964fb9..a9a83f8 100644 --- a/scenarios/preset/helpers/aws_cli.py +++ b/scenarios/preset/helpers/aws_cli.py @@ -11,9 +11,9 @@ def create_bucket(endpoint, versioning, location): bucket_name = str(uuid.uuid4()) cmd_line = f"aws --no-verify-ssl s3api create-bucket --bucket {bucket_name} " \ - f"--endpoint http://{endpoint} {location}" + f"--endpoint https://{endpoint} {location}" cmd_line_ver = f"aws --no-verify-ssl s3api put-bucket-versioning --bucket {bucket_name} " \ - f"--versioning-configuration Status=Enabled --endpoint http://{endpoint} " + f"--versioning-configuration Status=Enabled --endpoint https://{endpoint} " out, success = execute_cmd(cmd_line) @@ -21,7 +21,7 @@ def create_bucket(endpoint, versioning, location): if "succeeded and you already own it" in out: bucket_create_marker = True else: - print(f" > Bucket {bucket_name} has not been created.") + print(f" > Bucket {bucket_name} has not been created:\n{out}") else: bucket_create_marker = True print(f"cmd: {cmd_line}") @@ -29,7 +29,7 @@ def create_bucket(endpoint, versioning, location): if bucket_create_marker and versioning == "True": out, success = execute_cmd(cmd_line_ver) if not success: - print(f" > Bucket versioning has not been applied for bucket {bucket_name}.") + print(f" > Bucket versioning has not been applied for bucket {bucket_name}:\n{out}") else: print(f" > Bucket versioning has been applied.") @@ -39,8 +39,8 @@ def create_bucket(endpoint, versioning, location): def upload_object(bucket, payload_filepath, endpoint): object_name = str(uuid.uuid4()) - cmd_line = f"aws s3api put-object --bucket {bucket} --key {object_name} " \ - f"--body {payload_filepath} --endpoint http://{endpoint}" + cmd_line = f"aws --no-verify-ssl s3api put-object --bucket {bucket} --key {object_name} " \ + f"--body {payload_filepath} --endpoint https://{endpoint}" out, success = execute_cmd(cmd_line) if not success: diff --git a/scenarios/preset/resolve_containers_in_preset.py b/scenarios/preset/resolve_containers_in_preset.py new file mode 100755 index 0000000..78e292c --- /dev/null +++ b/scenarios/preset/resolve_containers_in_preset.py @@ -0,0 +1,29 @@ +#!/usr/bin/python3 + +import argparse +import json +import requests + +parser = argparse.ArgumentParser() +parser.add_argument('--endpoint', help='Endpoint of the S3 gateway') +parser.add_argument('--preset_file', help='JSON file path with s3 preset') + +args = parser.parse_args() + +def main(): + with open(args.preset_file) as f: + preset_text = f.read() + + preset = json.loads(preset_text) + + containers = [] + for bucket in preset.get('buckets'): + resp = requests.head(f'{args.endpoint}/{bucket}', verify=False) + containers.append(resp.headers['X-Container-Id']) + + preset['containers'] = containers + with open(args.preset_file, 'w+') as f: + json.dump(preset, f, ensure_ascii=False, indent=2) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scenarios/run_scenarios.md b/scenarios/run_scenarios.md index ad2935a..9d6bdb9 100644 --- a/scenarios/run_scenarios.md +++ b/scenarios/run_scenarios.md @@ -6,7 +6,7 @@ $ ./k6 run -e ENV_FILE=.env some-scenario.js ``` -## Common options for gRPC, local, HTTP, S3 scenarios: +## Common options for all scenarios: Scenarios `grpc.js`, `local.js`, `http.js` and `s3.js` support the following options: * `DURATION` - duration of scenario in seconds. @@ -124,6 +124,29 @@ Options (in addition to the common options): * `SLEEP_DELETE` - time interval (in seconds) between deleting VU iterations. * `OBJ_NAME` - if specified, this name will be used for all write operations instead of random generation. +## S3 Local + +1. Follow steps 1. and 2. from the normal S3 scenario in order to obtain credentials and a preset file with the information about the buckets and objects that were pre-created. +2. Assuming the preset file was named `pregen.json`, we need to populate the bucket-to-container mapping before running the local S3 scenario: + +**WARNING**: Be aware that this command will overwrite the `containers` list field in `pregen.json` file. Make a backup if needed beforehand. + +```shell +$ ./scenarios/preset/resolve_containers_in_preset.py --endpoint s3host:8080 --preset_file pregen.json +``` + +After this, the `pregen.json` file will contain a `containers` list field the same length as `buckets`, which is the mapping of bucket name to container ID in the order they appear. + +3. Execute the scenario with the desired options. For example: +```shell +$ ./k6 run -e DURATION=60 -e WRITE_OBJ_SIZE=8192 -e READERS=20 -e WRITERS=20 -e CONFIG_FILE=/path/to/node/config.yml -e PREGEN_JSON=pregen.json scenarios/s3local.js +``` + +Note that the `s3local` scenario currently does not support deleters. + +Options (in addition to the common options): + * `OBJ_NAME` - if specified, this name will be used for all write operations instead of random generation. + ## Verify This scenario allows to verify that objects created by a previous run are really stored in the system and their data is not corrupted. Running this scenario assumes that you've already run gRPC or HTTP or S3 scenario with option `REGISTRY_FILE`. diff --git a/scenarios/s3local.js b/scenarios/s3local.js new file mode 100644 index 0000000..8dcb75a --- /dev/null +++ b/scenarios/s3local.js @@ -0,0 +1,132 @@ +import datagen from 'k6/x/frostfs/datagen'; +import logging from 'k6/x/frostfs/logging'; +import registry from 'k6/x/frostfs/registry'; +import s3local from 'k6/x/frostfs/s3local'; +import { SharedArray } from 'k6/data'; +import { textSummary } from './libs/k6-summary-0.0.2.js'; +import { parseEnv } from './libs/env-parser.js'; + +parseEnv(); + +const obj_list = new SharedArray('obj_list', function () { + return JSON.parse(open(__ENV.PREGEN_JSON)).objects; +}); + +const container_list = new SharedArray('container_list', function () { + return JSON.parse(open(__ENV.PREGEN_JSON)).containers; +}); + +const bucket_list = new SharedArray('bucket_list', function () { + return JSON.parse(open(__ENV.PREGEN_JSON)).buckets; +}); + +function bucket_mapping() { + if (container_list.length != bucket_list.length) { + throw 'The number of containers and buckets in the preset file must be the same.'; + } + let mapping = {}; + for (let i = 0; i < container_list.length; ++i) { + mapping[bucket_list[i]] = container_list[i]; + } + return mapping; +} + +const read_size = JSON.parse(open(__ENV.PREGEN_JSON)).obj_size; +const summary_json = __ENV.SUMMARY_JSON || "/tmp/summary.json"; + +const config_file = __ENV.CONFIG_FILE; +const s3_client = s3local.connect(config_file, {}, bucket_mapping()); +const log = logging.new().withField("config", config_file); + +const registry_enabled = !!__ENV.REGISTRY_FILE; +const obj_registry = registry_enabled ? registry.open(__ENV.REGISTRY_FILE) : undefined; + +const duration = __ENV.DURATION; + +const generator = datagen.generator(1024 * parseInt(__ENV.WRITE_OBJ_SIZE)); + +const scenarios = {}; + +const write_vu_count = parseInt(__ENV.WRITERS || '0'); +if (write_vu_count > 0) { + scenarios.write = { + executor: 'constant-vus', + vus: write_vu_count, + duration: `${duration}s`, + exec: 'obj_write', + gracefulStop: '5s', + }; +} + +const read_vu_count = parseInt(__ENV.READERS || '0'); +if (read_vu_count > 0) { + scenarios.read = { + executor: 'constant-vus', + vus: read_vu_count, + duration: `${duration}s`, + exec: 'obj_read', + gracefulStop: '5s', + }; +} + +export const options = { + scenarios, + setupTimeout: '5s', +}; + +export function setup() { + const total_vu_count = write_vu_count + read_vu_count; + + console.log(`Pregenerated buckets: ${bucket_list.length}`); + console.log(`Pregenerated read object size: ${read_size}`); + console.log(`Pregenerated total objects: ${obj_list.length}`); + console.log(`Reading VUs: ${read_vu_count}`); + console.log(`Writing VUs: ${write_vu_count}`); + console.log(`Total VUs: ${total_vu_count}`); +} + +export function teardown(data) { + if (obj_registry) { + obj_registry.close(); + } +} + +export function handleSummary(data) { + return { + 'stdout': textSummary(data, { indent: ' ', enableColors: false }), + [summary_json]: JSON.stringify(data), + }; + } + +export function obj_write() { + const key = __ENV.OBJ_NAME || uuidv4(); + const bucket = bucket_list[Math.floor(Math.random() * bucket_list.length)]; + + const { payload, hash } = generator.genPayload(registry_enabled); + const resp = s3_client.put(bucket, key, payload); + if (!resp.success) { + log.withFields({bucket: bucket, key: key}).error(resp.error); + return; + } + + if (obj_registry) { + obj_registry.addObject("", "", bucket, key, hash); + } +} + +export function obj_read() { + const obj = obj_list[Math.floor(Math.random() * obj_list.length)]; + + const resp = s3_client.get(obj.bucket, obj.object); + if (!resp.success) { + log.withFields({bucket: obj.bucket, key: obj.object}).error(resp.error); + } +} + +export function uuidv4() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + let r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} +