Compare commits

...

4 commits

Author SHA1 Message Date
f72d9ecf2a config: Rename .yaml to .yml
Make them consistent across all our repos.
2023-03-30 19:05:34 +03:00
6c68e21777 [#69] Update SDK to fix handle request canceling
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-03-30 15:56:57 +03:00
a025f2e9c5 [#59] tree: Make interface for tree service client
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-03-29 09:34:10 +03:00
bd3164c57f [#68] Fix pre-commit issues
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-03-24 16:22:06 +03:00
34 changed files with 1353 additions and 792 deletions

1
.gitignore vendored
View file

@ -28,4 +28,3 @@ debian/files
debian/*.log debian/*.log
debian/*.substvars debian/*.substvars
debian/frostfs-s3-gw/ debian/frostfs-s3-gw/

View file

@ -17,6 +17,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/resolver" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/resolver"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/tree"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
@ -88,7 +89,7 @@ func prepareHandlerContext(t *testing.T) *handlerContext {
Caches: layer.DefaultCachesConfigs(zap.NewExample()), Caches: layer.DefaultCachesConfigs(zap.NewExample()),
AnonKey: layer.AnonymousKey{Key: key}, AnonKey: layer.AnonymousKey{Key: key},
Resolver: testResolver, Resolver: testResolver,
TreeService: layer.NewTreeService(), TreeService: NewTreeServiceMock(t),
} }
var pp netmap.PlacementPolicy var pp netmap.PlacementPolicy
@ -113,6 +114,12 @@ func prepareHandlerContext(t *testing.T) *handlerContext {
} }
} }
func NewTreeServiceMock(t *testing.T) *tree.Tree {
memCli, err := tree.NewTreeServiceClientMemory()
require.NoError(t, err)
return tree.NewTree(memCli)
}
func createTestBucket(hc *handlerContext, bktName string) *data.BucketInfo { func createTestBucket(hc *handlerContext, bktName string) *data.BucketInfo {
_, err := hc.MockedPool().CreateContainer(hc.Context(), layer.PrmContainerCreate{ _, err := hc.MockedPool().CreateContainer(hc.Context(), layer.PrmContainerCreate{
Creator: hc.owner, Creator: hc.owner,

View file

@ -753,12 +753,3 @@ func periodicXMLWriter(w io.Writer, dur time.Duration) (stop func() bool) {
return stop return stop
} }
// periodicWriterErrorSender returns handler function to send error. If header is
// alreay written by periodic XML writer, do not send HTTP and XML headers.
func (h *handler) periodicWriterErrorSender(headerWritten bool) func(http.ResponseWriter, string, *api.ReqInfo, error, ...zap.Field) {
if headerWritten {
return h.logAndSendErrorNoHeader
}
return h.logAndSendError
}

View file

@ -25,12 +25,15 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/xml" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/xml"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/metrics" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/metrics"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/tree"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/spf13/viper" "github.com/spf13/viper"
"go.uber.org/zap" "go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
) )
type ( type (
@ -110,10 +113,12 @@ func (a *App) initLayer(ctx context.Context) {
a.initResolver() a.initResolver()
treeServiceEndpoint := a.cfg.GetString(cfgTreeServiceEndpoint) treeServiceEndpoint := a.cfg.GetString(cfgTreeServiceEndpoint)
treeService, err := frostfs.NewTreeClient(ctx, treeServiceEndpoint, a.key) grpcDialOpt := grpc.WithTransportCredentials(insecure.NewCredentials())
treeGRPCClient, err := tree.NewTreeServiceClientGRPC(ctx, treeServiceEndpoint, a.key, grpcDialOpt)
if err != nil { if err != nil {
a.log.Fatal("failed to create tree service", zap.Error(err)) a.log.Fatal("failed to create tree service", zap.Error(err))
} }
treeService := tree.NewTree(treeGRPCClient)
a.log.Info("init tree service", zap.String("endpoint", treeServiceEndpoint)) a.log.Info("init tree service", zap.String("endpoint", treeServiceEndpoint))
// prepare random key for anonymous requests // prepare random key for anonymous requests

View file

@ -27,4 +27,3 @@ message Tokens {
bytes bearerToken = 2 [json_name = "bearerToken"]; bytes bearerToken = 2 [json_name = "bearerToken"];
repeated bytes sessionTokens = 3 [json_name = "sessionTokens"]; repeated bytes sessionTokens = 3 [json_name = "sessionTokens"];
} }

View file

@ -1,4 +1,4 @@
config/config.yaml etc/frostfs/s3 config/config.yml etc/frostfs/s3
config/rules.json var/lib/frostfs/s3 config/rules.json var/lib/frostfs/s3
bin/frostfs-s3-gw usr/bin bin/frostfs-s3-gw usr/bin
bin/frostfs-s3-authmate usr/bin bin/frostfs-s3-authmate usr/bin

10
debian/frostfs-s3-gw.postinst vendored Normal file → Executable file
View file

@ -24,14 +24,14 @@ case "$1" in
id -u frostfs-$USERNAME >/dev/null 2>&1 || useradd -s /usr/sbin/nologin -d /var/lib/frostfs/s3 --system -M -U -c "FrostFS S3 gateway" frostfs-$USERNAME id -u frostfs-$USERNAME >/dev/null 2>&1 || useradd -s /usr/sbin/nologin -d /var/lib/frostfs/s3 --system -M -U -c "FrostFS S3 gateway" frostfs-$USERNAME
if ! dpkg-statoverride --list /etc/frostfs/$USERNAME >/dev/null; then if ! dpkg-statoverride --list /etc/frostfs/$USERNAME >/dev/null; then
chown -f -R root:frostfs-$USERNAME /etc/frostfs/$USERNAME chown -f -R root:frostfs-$USERNAME /etc/frostfs/$USERNAME
chown -f root:frostfs-$USERNAME /etc/frostfs/$USERNAME/config.yaml || true chown -f root:frostfs-$USERNAME /etc/frostfs/$USERNAME/config.yml || true
chmod -f 0750 /etc/frostfs/$USERNAME chmod -f 0750 /etc/frostfs/$USERNAME
chmod -f 0640 /etc/frostfs/$USERNAME/config.yaml || true chmod -f 0640 /etc/frostfs/$USERNAME/config.yml || true
fi fi
USERDIR=$(getent passwd "frostfs-$USERNAME" | cut -d: -f6) USERDIR=$(getent passwd "frostfs-$USERNAME" | cut -d: -f6)
if ! dpkg-statoverride --list frostfs-$USERDIR >/dev/null; then if ! dpkg-statoverride --list frostfs-"$USERDIR" >/dev/null; then
chown -f frostfs-$USERNAME: $USERDIR chown -f frostfs-$USERNAME: "$USERDIR"
chown -f frostfs-$USERNAME: $USERDIR/rules.json chown -f frostfs-$USERNAME: "$USERDIR"/rules.json
fi fi
;; ;;

0
debian/frostfs-s3-gw.postrm vendored Normal file → Executable file
View file

0
debian/frostfs-s3-gw.preinst vendored Normal file → Executable file
View file

0
debian/frostfs-s3-gw.prerm vendored Normal file → Executable file
View file

View file

@ -4,7 +4,7 @@ Requires=network.target
[Service] [Service]
Type=simple Type=simple
ExecStart=/usr/bin/frostfs-s3-gw --config /etc/frostfs/s3/config.yaml ExecStart=/usr/bin/frostfs-s3-gw --config /etc/frostfs/s3/config.yml
User=frostfs-s3 User=frostfs-s3
Group=frostfs-s3 Group=frostfs-s3
WorkingDirectory=/var/lib/frostfs/s3 WorkingDirectory=/var/lib/frostfs/s3

2
debian/rules vendored
View file

@ -12,5 +12,3 @@ override_dh_installsystemd:
override_dh_installchangelogs: override_dh_installchangelogs:
dh_installchangelogs -k CHANGELOG.md dh_installchangelogs -k CHANGELOG.md

View file

@ -101,13 +101,13 @@ Pprof and Prometheus are integrated into the gateway. To enable them, use `--ppr
## YAML file and environment variables ## YAML file and environment variables
Example of a YAML configuration file: [yaml-example](/config/config.yaml) Example of a YAML configuration file: [yaml-example](/config/config.yml)
Examples of environment variables: [env-example](/config/config.env). Examples of environment variables: [env-example](/config/config.env).
A path to a configuration file can be specified with `--config` parameter: A path to a configuration file can be specified with `--config` parameter:
```shell ```shell
$ frostfs-s3-gw --config your-config.yaml $ frostfs-s3-gw --config your-config.yml
``` ```
### Multiple configs ### Multiple configs
@ -118,13 +118,13 @@ You can either provide several files with repeating `--config` flag or provide p
Also, you can combine these flags: Also, you can combine these flags:
```shell ```shell
$ frostfs-s3-gw --config ./config/config.yaml --config /your/partial/config.yaml --config-dir ./config/dir $ frostfs-s3-gw --config ./config/config.yml --config /your/partial/config.yml --config-dir ./config/dir
``` ```
**Note:** next file in `--config` flag overwrites values from the previous one. **Note:** next file in `--config` flag overwrites values from the previous one.
Files from `--config-dir` directory overwrite values from `--config` files. Files from `--config-dir` directory overwrite values from `--config` files.
So the command above run `frostfs-s3-gw` to listen on `0.0.0.0:8080` address (value from `./config/config.yaml`), So the command above run `frostfs-s3-gw` to listen on `0.0.0.0:8080` address (value from `./config/config.yml`),
applies parameters from `/your/partial/config.yaml`, applies parameters from `/your/partial/config.yml`,
enable pprof (value from `./config/dir/pprof.yaml`) and prometheus (value from `./config/dir/prometheus.yaml`). enable pprof (value from `./config/dir/pprof.yaml`) and prometheus (value from `./config/dir/prometheus.yaml`).
### Reload on SIGHUP ### Reload on SIGHUP
@ -141,7 +141,7 @@ $ kill -s SIGHUP <app_pid>
Example: Example:
```shell ```shell
$ ./bin/frostfs-s3-gw --config config.yaml &> s3.log & $ ./bin/frostfs-s3-gw --config config.yml &> s3.log &
[1] 998346 [1] 998346
$ cat s3.log $ cat s3.log

View file

@ -77,72 +77,72 @@ Compatibility: 30/25/29 out of 33
Compatibility: 33/43/37 out of 64 Compatibility: 33/43/37 out of 64
| | Test | s3-gw | minio | aws s3 | | | Test | s3-gw | minio | aws s3 |
|----|------------------------------------------------------------------------------------------------|-------------|-------|--------| |-----|------------------------------------------------------------------------------------------------|-------------|-------|--------|
| 1 | s3tests_boto3.functional.test_s3.test_put_object_ifmatch_good | ok | ok | ERROR | | 1 | s3tests_boto3.functional.test_s3.test_put_object_ifmatch_good | ok | ok | ERROR |
| 2 | s3tests_boto3.functional.test_s3.test_put_object_ifmatch_failed | FAIL | FAIL | FAIL | | 2 | s3tests_boto3.functional.test_s3.test_put_object_ifmatch_failed | FAIL | FAIL | FAIL |
| 3 | s3tests_boto3.functional.test_s3.test_put_object_ifmatch_overwrite_existed_good | ok | ok | ERROR | | 3 | s3tests_boto3.functional.test_s3.test_put_object_ifmatch_overwrite_existed_good | ok | ok | ERROR |
| 4 | s3tests_boto3.functional.test_s3.test_put_object_ifmatch_nonexisted_failed | FAIL | FAIL | FAIL | | 4 | s3tests_boto3.functional.test_s3.test_put_object_ifmatch_nonexisted_failed | FAIL | FAIL | FAIL |
| 5 | s3tests_boto3.functional.test_s3.test_put_object_ifnonmatch_good | ok | ok | ERROR | | 5 | s3tests_boto3.functional.test_s3.test_put_object_ifnonmatch_good | ok | ok | ERROR |
| 6 | s3tests_boto3.functional.test_s3.test_put_object_ifnonmatch_failed | FAIL | FAIL | FAIL | | 6 | s3tests_boto3.functional.test_s3.test_put_object_ifnonmatch_failed | FAIL | FAIL | FAIL |
| 7 | s3tests_boto3.functional.test_s3.test_put_object_ifnonmatch_nonexisted_good | ok | ok | ERROR | | 7 | s3tests_boto3.functional.test_s3.test_put_object_ifnonmatch_nonexisted_good | ok | ok | ERROR |
| 8 | s3tests_boto3.functional.test_s3.test_put_object_ifnonmatch_overwrite_existed_failed | FAIL | FAIL | FAIL | | 8 | s3tests_boto3.functional.test_s3.test_put_object_ifnonmatch_overwrite_existed_failed | FAIL | FAIL | FAIL |
| 9 | s3tests_boto3.functional.test_headers.test_object_create_bad_md5_invalid_short | UNSUPPORTED | ok | ok | | 9 | s3tests_boto3.functional.test_headers.test_object_create_bad_md5_invalid_short | UNSUPPORTED | ok | ok |
| 10 | s3tests_boto3.functional.test_headers.test_object_create_bad_md5_bad | UNSUPPORTED | ok | ok | | 10 | s3tests_boto3.functional.test_headers.test_object_create_bad_md5_bad | UNSUPPORTED | ok | ok |
| 11 | s3tests_boto3.functional.test_headers.test_object_create_bad_md5_empty | UNSUPPORTED | ok | ok | | 11 | s3tests_boto3.functional.test_headers.test_object_create_bad_md5_empty | UNSUPPORTED | ok | ok |
| 12 | s3tests_boto3.functional.test_headers.test_object_create_bad_md5_none | ok | ok | ok | | 12 | s3tests_boto3.functional.test_headers.test_object_create_bad_md5_none | ok | ok | ok |
| 13 | s3tests_boto3.functional.test_headers.test_object_create_bad_expect_mismatch | ERROR | ERROR | ok | | 13 | s3tests_boto3.functional.test_headers.test_object_create_bad_expect_mismatch | ERROR | ERROR | ok |
| 14 | s3tests_boto3.functional.test_headers.test_object_create_bad_expect_empty | ok | ok | ok | | 14 | s3tests_boto3.functional.test_headers.test_object_create_bad_expect_empty | ok | ok | ok |
| 15 | s3tests_boto3.functional.test_headers.test_object_create_bad_expect_none | ok | ok | ok | | 15 | s3tests_boto3.functional.test_headers.test_object_create_bad_expect_none | ok | ok | ok |
| 16 | s3tests_boto3.functional.test_headers.test_object_create_bad_contentlength_empty | FAIL | FAIL | ok | | 16 | s3tests_boto3.functional.test_headers.test_object_create_bad_contentlength_empty | FAIL | FAIL | ok |
| 17 | s3tests_boto3.functional.test_headers.test_object_create_bad_contentlength_negative | ok | ok | ok | | 17 | s3tests_boto3.functional.test_headers.test_object_create_bad_contentlength_negative | ok | ok | ok |
| 18 | s3tests_boto3.functional.test_headers.test_object_create_bad_contentlength_none | FAIL | FAIL | FAIL | | 18 | s3tests_boto3.functional.test_headers.test_object_create_bad_contentlength_none | FAIL | FAIL | FAIL |
| 19 | s3tests_boto3.functional.test_headers.test_object_create_bad_contentlength_mismatch_above | ERROR | ERROR | ERROR | | 19 | s3tests_boto3.functional.test_headers.test_object_create_bad_contentlength_mismatch_above | ERROR | ERROR | ERROR |
| 20 | s3tests_boto3.functional.test_headers.test_object_create_bad_contenttype_invalid | ok | ok | ok | | 20 | s3tests_boto3.functional.test_headers.test_object_create_bad_contenttype_invalid | ok | ok | ok |
| 21 | s3tests_boto3.functional.test_headers.test_object_create_bad_contenttype_empty | ok | ok | ok | | 21 | s3tests_boto3.functional.test_headers.test_object_create_bad_contenttype_empty | ok | ok | ok |
| 22 | s3tests_boto3.functional.test_headers.test_object_create_bad_contenttype_none | ok | ok | ok | | 22 | s3tests_boto3.functional.test_headers.test_object_create_bad_contenttype_none | ok | ok | ok |
| 23 | s3tests_boto3.functional.test_headers.test_object_create_bad_authorization_empty | FAIL | FAIL | FAIL | | 23 | s3tests_boto3.functional.test_headers.test_object_create_bad_authorization_empty | FAIL | FAIL | FAIL |
| 24 | s3tests_boto3.functional.test_headers.test_object_create_date_and_amz_date | ERROR | ERROR | ERROR | | 24 | s3tests_boto3.functional.test_headers.test_object_create_date_and_amz_date | ERROR | ERROR | ERROR |
| 25 | s3tests_boto3.functional.test_headers.test_object_create_amz_date_and_no_date | ERROR | ERROR | ERROR | | 25 | s3tests_boto3.functional.test_headers.test_object_create_amz_date_and_no_date | ERROR | ERROR | ERROR |
| 26 | s3tests_boto3.functional.test_headers.test_object_create_bad_authorization_none | FAIL | FAIL | FAIL | | 26 | s3tests_boto3.functional.test_headers.test_object_create_bad_authorization_none | FAIL | FAIL | FAIL |
| 27 | s3tests_boto3.functional.test_headers.test_object_create_bad_md5_invalid_garbage_aws2 | UNSUPPORTED | ok | ok | | 27 | s3tests_boto3.functional.test_headers.test_object_create_bad_md5_invalid_garbage_aws2 | UNSUPPORTED | ok | ok |
| 28 | s3tests_boto3.functional.test_headers.test_object_create_bad_contentlength_mismatch_below_aws2 | FAIL | FAIL | ok | | 28 | s3tests_boto3.functional.test_headers.test_object_create_bad_contentlength_mismatch_below_aws2 | FAIL | FAIL | ok |
| 29 | s3tests_boto3.functional.test_headers.test_object_create_bad_authorization_incorrect_aws2 | FAIL | FAIL | FAIL | | 29 | s3tests_boto3.functional.test_headers.test_object_create_bad_authorization_incorrect_aws2 | FAIL | FAIL | FAIL |
| 30 | s3tests_boto3.functional.test_headers.test_object_create_bad_authorization_invalid_aws2 | FAIL | FAIL | FAIL | | 30 | s3tests_boto3.functional.test_headers.test_object_create_bad_authorization_invalid_aws2 | FAIL | FAIL | FAIL |
| 31 | s3tests_boto3.functional.test_headers.test_object_create_bad_ua_empty_aws2 | ERROR | ok | ok | | 31 | s3tests_boto3.functional.test_headers.test_object_create_bad_ua_empty_aws2 | ERROR | ok | ok |
| 32 | s3tests_boto3.functional.test_headers.test_object_create_bad_ua_none_aws2 | ERROR | ok | ok | | 32 | s3tests_boto3.functional.test_headers.test_object_create_bad_ua_none_aws2 | ERROR | ok | ok |
| 33 | s3tests_boto3.functional.test_headers.test_object_create_bad_date_invalid_aws2 | FAIL | FAIL | ok | | 33 | s3tests_boto3.functional.test_headers.test_object_create_bad_date_invalid_aws2 | FAIL | FAIL | ok |
| 34 | s3tests_boto3.functional.test_headers.test_object_create_bad_date_empty_aws2 | FAIL | FAIL | ok | | 34 | s3tests_boto3.functional.test_headers.test_object_create_bad_date_empty_aws2 | FAIL | FAIL | ok |
| 35 | s3tests_boto3.functional.test_headers.test_object_create_bad_date_none_aws2 | FAIL | FAIL | FAIL | | 35 | s3tests_boto3.functional.test_headers.test_object_create_bad_date_none_aws2 | FAIL | FAIL | FAIL |
| 36 | s3tests_boto3.functional.test_headers.test_object_create_bad_date_before_today_aws2 | FAIL | ok | ok | | 36 | s3tests_boto3.functional.test_headers.test_object_create_bad_date_before_today_aws2 | FAIL | ok | ok |
| 37 | s3tests_boto3.functional.test_headers.test_object_create_bad_date_before_epoch_aws2 | FAIL | FAIL | ok | | 37 | s3tests_boto3.functional.test_headers.test_object_create_bad_date_before_epoch_aws2 | FAIL | FAIL | ok |
| 38 | s3tests_boto3.functional.test_headers.test_object_create_bad_date_after_end_aws2 | FAIL | ok | ok | | 38 | s3tests_boto3.functional.test_headers.test_object_create_bad_date_after_end_aws2 | FAIL | ok | ok |
| 39 | s3tests_boto3.functional.test_s3.test_object_anon_put | ok | ok | ok | | 39 | s3tests_boto3.functional.test_s3.test_object_anon_put | ok | ok | ok |
| 40 | s3tests_boto3.functional.test_s3.test_object_put_authenticated | ok | ok | ok | | 40 | s3tests_boto3.functional.test_s3.test_object_put_authenticated | ok | ok | ok |
| 41 | s3tests_boto3.functional.test_s3.test_object_raw_put_authenticated_expired | ok | FAIL | FAIL | | 41 | s3tests_boto3.functional.test_s3.test_object_raw_put_authenticated_expired | ok | FAIL | FAIL |
| 42 | s3tests_boto3.functional.test_s3.test_object_write_file | ok | ok | ok | | 42 | s3tests_boto3.functional.test_s3.test_object_write_file | ok | ok | ok |
| 43 | s3tests_boto3.functional.test_s3.test_object_write_check_etag | FAIL | ok | ok | | 43 | s3tests_boto3.functional.test_s3.test_object_write_check_etag | FAIL | ok | ok |
| 44 | s3tests_boto3.functional.test_s3.test_object_write_cache_control | ok | ok | ok | | 44 | s3tests_boto3.functional.test_s3.test_object_write_cache_control | ok | ok | ok |
| 45 | s3tests_boto3.functional.test_s3.test_object_write_expires | ok | ok | ok | | 45 | s3tests_boto3.functional.test_s3.test_object_write_expires | ok | ok | ok |
| 46 | s3tests_boto3.functional.test_s3.test_object_write_read_update_read_delete | ok | ok | ok | | 46 | s3tests_boto3.functional.test_s3.test_object_write_read_update_read_delete | ok | ok | ok |
| 47 | s3tests_boto3.functional.test_s3.test_object_set_get_metadata_none_to_good | ok | ok | ok | | 47 | s3tests_boto3.functional.test_s3.test_object_set_get_metadata_none_to_good | ok | ok | ok |
| 48 | s3tests_boto3.functional.test_s3.test_object_set_get_metadata_none_to_empty | ERROR | ok | ok | | 48 | s3tests_boto3.functional.test_s3.test_object_set_get_metadata_none_to_empty | ERROR | ok | ok |
| 49 | s3tests_boto3.functional.test_s3.test_object_set_get_metadata_overwrite_to_empty | ERROR | ok | ok | | 49 | s3tests_boto3.functional.test_s3.test_object_set_get_metadata_overwrite_to_empty | ERROR | ok | ok |
| 50 | s3tests_boto3.functional.test_s3.test_object_set_get_non_utf8_metadata | ok | ok | FAIL | | 50 | s3tests_boto3.functional.test_s3.test_object_set_get_non_utf8_metadata | ok | ok | FAIL |
| 51 | s3tests_boto3.functional.test_s3.test_object_set_get_metadata_empty_to_unreadable_prefix | ok | ok | FAIL | | 51 | s3tests_boto3.functional.test_s3.test_object_set_get_metadata_empty_to_unreadable_prefix | ok | ok | FAIL |
| 52 | s3tests_boto3.functional.test_s3.test_object_set_get_metadata_empty_to_unreadable_suffix | ok | ok | FAIL | | 52 | s3tests_boto3.functional.test_s3.test_object_set_get_metadata_empty_to_unreadable_suffix | ok | ok | FAIL |
| 53 | s3tests_boto3.functional.test_s3.test_object_set_get_metadata_empty_to_unreadable_infix | ok | ok | FAIL | | 53 | s3tests_boto3.functional.test_s3.test_object_set_get_metadata_empty_to_unreadable_infix | ok | ok | FAIL |
| 54 | s3tests_boto3.functional.test_s3.test_object_metadata_replaced_on_put | ok | ok | ok | | 54 | s3tests_boto3.functional.test_s3.test_object_metadata_replaced_on_put | ok | ok | ok |
| 55 | s3tests_boto3.functional.test_s3.test_object_write_to_nonexist_bucket | ok | ok | ok | | 55 | s3tests_boto3.functional.test_s3.test_object_write_to_nonexist_bucket | ok | ok | ok |
| 56 | s3tests_boto3.functional.test_s3.test_atomic_write_1mb | ok | ok | ok | | 56 | s3tests_boto3.functional.test_s3.test_atomic_write_1mb | ok | ok | ok |
| 57 | s3tests_boto3.functional.test_s3.test_atomic_write_4mb | ok | ok | ok | | 57 | s3tests_boto3.functional.test_s3.test_atomic_write_4mb | ok | ok | ok |
| 58 | s3tests_boto3.functional.test_s3.test_atomic_write_8mb | ok | ok | ok | | 58 | s3tests_boto3.functional.test_s3.test_atomic_write_8mb | ok | ok | ok |
| 59 | s3tests_boto3.functional.test_s3.test_atomic_dual_write_1mb | ok | ok | ERROR | | 59 | s3tests_boto3.functional.test_s3.test_atomic_dual_write_1mb | ok | ok | ERROR |
| 60 | s3tests_boto3.functional.test_s3.test_atomic_dual_write_4mb | ok | ok | ERROR | | 60 | s3tests_boto3.functional.test_s3.test_atomic_dual_write_4mb | ok | ok | ERROR |
| 61 | s3tests_boto3.functional.test_s3.test_atomic_dual_write_8mb | ok | ok | ERROR | | 61 | s3tests_boto3.functional.test_s3.test_atomic_dual_write_8mb | ok | ok | ERROR |
| 62 | s3tests_boto3.functional.test_s3.test_atomic_conditional_write_1mb | ok | ok | ERROR | | 62 | s3tests_boto3.functional.test_s3.test_atomic_conditional_write_1mb | ok | ok | ERROR |
| 63 | s3tests_boto3.functional.test_s3.test_atomic_dual_conditional_write_1mb | FAIL | FAIL | FAIL | | 63 | s3tests_boto3.functional.test_s3.test_atomic_dual_conditional_write_1mb | FAIL | FAIL | FAIL |
| 64 | s3tests_boto3.functional.test_s3.test_atomic_write_bucket_gone | ok | ok | ok | | 64 | s3tests_boto3.functional.test_s3.test_atomic_write_bucket_gone | ok | ok | ok |
## PostObject ## PostObject
@ -342,30 +342,30 @@ Compatibility: 4/5/29 out of 29
Compatibility: 19/15/19 out of 22 Compatibility: 19/15/19 out of 22
| | Test | s3-gw | minio | aws s3 | | | Test | s3-gw | minio | aws s3 |
|----|----------------------------------------------------------------------------------|-------|-------|--------| |-----|----------------------------------------------------------------------------------|-------|-------|--------|
| 1 | s3tests_boto3.functional.test_s3.test_multipart_upload_empty | ok | FAIL | FAIL | | 1 | s3tests_boto3.functional.test_s3.test_multipart_upload_empty | ok | FAIL | FAIL |
| 2 | s3tests_boto3.functional.test_s3.test_multipart_upload_small | ERROR | ERROR | ok | | 2 | s3tests_boto3.functional.test_s3.test_multipart_upload_small | ERROR | ERROR | ok |
| 3 | s3tests_boto3.functional.test_s3.test_multipart_copy_small | ok | ok | ok | | 3 | s3tests_boto3.functional.test_s3.test_multipart_copy_small | ok | ok | ok |
| 4 | s3tests_boto3.functional.test_s3.test_multipart_copy_invalid_range | ok | FAIL | FAIL | | 4 | s3tests_boto3.functional.test_s3.test_multipart_copy_invalid_range | ok | FAIL | FAIL |
| 5 | s3tests_boto3.functional.test_s3.test_multipart_copy_improper_range | ok | ok | ok | | 5 | s3tests_boto3.functional.test_s3.test_multipart_copy_improper_range | ok | ok | ok |
| 6 | s3tests_boto3.functional.test_s3.test_multipart_copy_without_range | ok | ok | ok | | 6 | s3tests_boto3.functional.test_s3.test_multipart_copy_without_range | ok | ok | ok |
| 7 | s3tests_boto3.functional.test_s3.test_multipart_copy_special_names | ok | ok | ok | | 7 | s3tests_boto3.functional.test_s3.test_multipart_copy_special_names | ok | ok | ok |
| 8 | s3tests_boto3.functional.test_s3.test_multipart_upload | ERROR | ERROR | ok | | 8 | s3tests_boto3.functional.test_s3.test_multipart_upload | ERROR | ERROR | ok |
| 9 | s3tests_boto3.functional.test_s3.test_multipart_upload_resend_part | ok | ok | ok | | 9 | s3tests_boto3.functional.test_s3.test_multipart_upload_resend_part | ok | ok | ok |
| 10 | s3tests_boto3.functional.test_s3.test_multipart_upload_multiple_sizes | ok | ok | ok | | 10 | s3tests_boto3.functional.test_s3.test_multipart_upload_multiple_sizes | ok | ok | ok |
| 11 | s3tests_boto3.functional.test_s3.test_multipart_copy_multiple_sizes | ok | ok | ok | | 11 | s3tests_boto3.functional.test_s3.test_multipart_copy_multiple_sizes | ok | ok | ok |
| 12 | s3tests_boto3.functional.test_s3.test_multipart_upload_size_too_small | ok | ok | ok | | 12 | s3tests_boto3.functional.test_s3.test_multipart_upload_size_too_small | ok | ok | ok |
| 13 | s3tests_boto3.functional.test_s3.test_multipart_upload_contents | ok | ok | ok | | 13 | s3tests_boto3.functional.test_s3.test_multipart_upload_contents | ok | ok | ok |
| 14 | s3tests_boto3.functional.test_s3.test_multipart_upload_overwrite_existing_object | ok | ok | ok | | 14 | s3tests_boto3.functional.test_s3.test_multipart_upload_overwrite_existing_object | ok | ok | ok |
| 15 | s3tests_boto3.functional.test_s3.test_abort_multipart_upload | ok | ok | ok | | 15 | s3tests_boto3.functional.test_s3.test_abort_multipart_upload | ok | ok | ok |
| 16 | s3tests_boto3.functional.test_s3.test_abort_multipart_upload_not_found | ok | ok | ok | | 16 | s3tests_boto3.functional.test_s3.test_abort_multipart_upload_not_found | ok | ok | ok |
| 17 | s3tests_boto3.functional.test_s3.test_list_multipart_upload | ok | ERROR | ok | | 17 | s3tests_boto3.functional.test_s3.test_list_multipart_upload | ok | ERROR | ok |
| 18 | s3tests_boto3.functional.test_s3.test_multipart_upload_missing_part | ok | ok | ok | | 18 | s3tests_boto3.functional.test_s3.test_multipart_upload_missing_part | ok | ok | ok |
| 19 | s3tests_boto3.functional.test_s3.test_multipart_upload_incorrect_etag | ok | ok | ok | | 19 | s3tests_boto3.functional.test_s3.test_multipart_upload_incorrect_etag | ok | ok | ok |
| 20 | s3tests_boto3.functional.test_s3.test_multipart_resend_first_finishes_last | ERROR | ERROR | ERROR | | 20 | s3tests_boto3.functional.test_s3.test_multipart_resend_first_finishes_last | ERROR | ERROR | ERROR |
| 21 | s3tests_boto3.functional.test_s3.test_atomic_multipart_upload_write | ok | ok | ok | | 21 | s3tests_boto3.functional.test_s3.test_atomic_multipart_upload_write | ok | ok | ok |
| 22 | s3tests_boto3.functional.test_s3.test_multipart_copy_versioned | ok | ERROR | ok | | 22 | s3tests_boto3.functional.test_s3.test_multipart_copy_versioned | ok | ERROR | ok |
Comments: in [PR](https://github.com/nspcc-dev/s3-tests/pull/5) Comments: in [PR](https://github.com/nspcc-dev/s3-tests/pull/5)
@ -373,118 +373,118 @@ Comments: in [PR](https://github.com/nspcc-dev/s3-tests/pull/5)
Compatibility: 9/6/8 out of 11 Compatibility: 9/6/8 out of 11
| | Test | s3-gw | minio | aws s3 | | | Test | s3-gw | minio | aws s3 |
|----|------------------------------------------------------------|-------|-------|--------| |-----|------------------------------------------------------------|-------|-------|--------|
| 1 | s3tests_boto3.functional.test_s3.test_set_bucket_tagging | FAIL | FAIL | FAIL | | 1 | s3tests_boto3.functional.test_s3.test_set_bucket_tagging | FAIL | FAIL | FAIL |
| 2 | s3tests_boto3.functional.test_s3.test_get_obj_tagging | ok | ok | ok | | 2 | s3tests_boto3.functional.test_s3.test_get_obj_tagging | ok | ok | ok |
| 3 | s3tests_boto3.functional.test_s3.test_get_obj_head_tagging | ok | ok | ok | | 3 | s3tests_boto3.functional.test_s3.test_get_obj_head_tagging | ok | ok | ok |
| 4 | s3tests_boto3.functional.test_s3.test_put_max_tags | ok | FAIL | ok | | 4 | s3tests_boto3.functional.test_s3.test_put_max_tags | ok | FAIL | ok |
| 5 | s3tests_boto3.functional.test_s3.test_put_excess_tags | FAIL | FAIL | FAIL | | 5 | s3tests_boto3.functional.test_s3.test_put_excess_tags | FAIL | FAIL | FAIL |
| 6 | s3tests_boto3.functional.test_s3.test_put_max_kvsize_tags | ok | ok | ok | | 6 | s3tests_boto3.functional.test_s3.test_put_max_kvsize_tags | ok | ok | ok |
| 7 | s3tests_boto3.functional.test_s3.test_put_excess_key_tags | ok | ok | ok | | 7 | s3tests_boto3.functional.test_s3.test_put_excess_key_tags | ok | ok | ok |
| 8 | s3tests_boto3.functional.test_s3.test_put_excess_val_tags | ok | ok | ok | | 8 | s3tests_boto3.functional.test_s3.test_put_excess_val_tags | ok | ok | ok |
| 9 | s3tests_boto3.functional.test_s3.test_put_modify_tags | ok | FAIL | FAIL | | 9 | s3tests_boto3.functional.test_s3.test_put_modify_tags | ok | FAIL | FAIL |
| 10 | s3tests_boto3.functional.test_s3.test_put_delete_tags | ok | ok | ok | | 10 | s3tests_boto3.functional.test_s3.test_put_delete_tags | ok | ok | ok |
| 11 | s3tests_boto3.functional.test_s3.test_put_obj_with_tags | ok | FAIL | ok | | 11 | s3tests_boto3.functional.test_s3.test_put_obj_with_tags | ok | FAIL | ok |
## Versioning ## Versioning
Compatibility: 23/19/24 out of 26 Compatibility: 23/19/24 out of 26
| | Test | s3-gw | minio | aws s3 | | | Test | s3-gw | minio | aws s3 |
|----|---------------------------------------------------------------------------------------------|-------|-------|--------| |-----|---------------------------------------------------------------------------------------------|-------|-------|--------|
| 1 | s3tests_boto3.functional.test_s3.test_versioning_bucket_create_suspend | ok | ok | ok | | 1 | s3tests_boto3.functional.test_s3.test_versioning_bucket_create_suspend | ok | ok | ok |
| 2 | s3tests_boto3.functional.test_s3.test_versioning_obj_create_read_remove | ok | ok | ok | | 2 | s3tests_boto3.functional.test_s3.test_versioning_obj_create_read_remove | ok | ok | ok |
| 3 | s3tests_boto3.functional.test_s3.test_versioning_obj_create_read_remove_head | ok | ok | ok | | 3 | s3tests_boto3.functional.test_s3.test_versioning_obj_create_read_remove_head | ok | ok | ok |
| 4 | s3tests_boto3.functional.test_s3.test_versioning_obj_plain_null_version_removal | ok | ok | ok | | 4 | s3tests_boto3.functional.test_s3.test_versioning_obj_plain_null_version_removal | ok | ok | ok |
| 5 | s3tests_boto3.functional.test_s3.test_versioning_obj_plain_null_version_overwrite | ok | ok | ok | | 5 | s3tests_boto3.functional.test_s3.test_versioning_obj_plain_null_version_overwrite | ok | ok | ok |
| 6 | s3tests_boto3.functional.test_s3.test_versioning_obj_plain_null_version_overwrite_suspended | ok | ok | ok | | 6 | s3tests_boto3.functional.test_s3.test_versioning_obj_plain_null_version_overwrite_suspended | ok | ok | ok |
| 7 | s3tests_boto3.functional.test_s3.test_versioning_obj_suspend_versions | ok | ok | ok | | 7 | s3tests_boto3.functional.test_s3.test_versioning_obj_suspend_versions | ok | ok | ok |
| 8 | s3tests_boto3.functional.test_s3.test_versioning_obj_create_versions_remove_all | ok | ok | ok | | 8 | s3tests_boto3.functional.test_s3.test_versioning_obj_create_versions_remove_all | ok | ok | ok |
| 9 | s3tests_boto3.functional.test_s3.test_versioning_obj_create_versions_remove_special_names | ok | ok | ok | | 9 | s3tests_boto3.functional.test_s3.test_versioning_obj_create_versions_remove_special_names | ok | ok | ok |
| 10 | s3tests_boto3.functional.test_s3.test_versioning_obj_create_overwrite_multipart | ok | ok | ok | | 10 | s3tests_boto3.functional.test_s3.test_versioning_obj_create_overwrite_multipart | ok | ok | ok |
| 11 | s3tests_boto3.functional.test_s3.test_versioning_obj_list_marker | ok | ok | ok | | 11 | s3tests_boto3.functional.test_s3.test_versioning_obj_list_marker | ok | ok | ok |
| 12 | s3tests_boto3.functional.test_s3.test_versioning_copy_obj_version | ok | ok | ok | | 12 | s3tests_boto3.functional.test_s3.test_versioning_copy_obj_version | ok | ok | ok |
| 13 | s3tests_boto3.functional.test_s3.test_versioning_multi_object_delete | ok | ok | ok | | 13 | s3tests_boto3.functional.test_s3.test_versioning_multi_object_delete | ok | ok | ok |
| 14 | s3tests_boto3.functional.test_s3.test_versioning_multi_object_delete_with_marker | ok | ok | ok | | 14 | s3tests_boto3.functional.test_s3.test_versioning_multi_object_delete_with_marker | ok | ok | ok |
| 15 | s3tests_boto3.functional.test_s3.test_versioning_multi_object_delete_with_marker_create | ok | ERROR | ok | | 15 | s3tests_boto3.functional.test_s3.test_versioning_multi_object_delete_with_marker_create | ok | ERROR | ok |
| 16 | s3tests_boto3.functional.test_s3.test_versioned_object_acl | FAIL | FAIL | FAIL | | 16 | s3tests_boto3.functional.test_s3.test_versioned_object_acl | FAIL | FAIL | FAIL |
| 17 | s3tests_boto3.functional.test_s3.test_versioned_object_acl_no_version_specified | FAIL | FAIL | FAIL | | 17 | s3tests_boto3.functional.test_s3.test_versioned_object_acl_no_version_specified | FAIL | FAIL | FAIL |
| 18 | s3tests_boto3.functional.test_s3.test_versioned_concurrent_object_create_concurrent_remove | ok | ok | ok | | 18 | s3tests_boto3.functional.test_s3.test_versioned_concurrent_object_create_concurrent_remove | ok | ok | ok |
| 19 | s3tests_boto3.functional.test_s3.test_versioned_concurrent_object_create_and_remove | ok | ok | ok | | 19 | s3tests_boto3.functional.test_s3.test_versioned_concurrent_object_create_and_remove | ok | ok | ok |
| 20 | s3tests_boto3.functional.test_s3.test_versioning_bucket_atomic_upload_return_version_id | ok | FAIL | ok | | 20 | s3tests_boto3.functional.test_s3.test_versioning_bucket_atomic_upload_return_version_id | ok | FAIL | ok |
| 21 | s3tests_boto3.functional.test_s3.test_versioning_bucket_multipart_upload_return_version_id | ok | FAIL | ok | | 21 | s3tests_boto3.functional.test_s3.test_versioning_bucket_multipart_upload_return_version_id | ok | FAIL | ok |
| 22 | s3tests_boto3.functional.test_s3.test_bucket_list_return_data_versioning | ERROR | ERROR | ok | | 22 | s3tests_boto3.functional.test_s3.test_bucket_list_return_data_versioning | ERROR | ERROR | ok |
| 23 | s3tests_boto3.functional.test_s3.test_object_copy_versioned_bucket | ok | ok | ok | | 23 | s3tests_boto3.functional.test_s3.test_object_copy_versioned_bucket | ok | ok | ok |
| 24 | s3tests_boto3.functional.test_s3.test_object_copy_versioned_url_encoding | ok | ok | ok | | 24 | s3tests_boto3.functional.test_s3.test_object_copy_versioned_url_encoding | ok | ok | ok |
| 25 | s3tests_boto3.functional.test_s3.test_object_copy_versioning_multipart_upload | ok | ok | ok | | 25 | s3tests_boto3.functional.test_s3.test_object_copy_versioning_multipart_upload | ok | ok | ok |
| 26 | s3tests_boto3.functional.test_s3.test_multipart_copy_versioned | ok | ERROR | ok | | 26 | s3tests_boto3.functional.test_s3.test_multipart_copy_versioned | ok | ERROR | ok |
## Bucket ## Bucket
Compatibility: 38/38/45 out of 59 Compatibility: 38/38/45 out of 59
| | Test | s3-gw | minio | aws s3 | | | Test | s3-gw | minio | aws s3 |
|----|----------------------------------------------------------------------------------------------|-------|-------|--------| |-----|----------------------------------------------------------------------------------------------|-------|-------|--------|
| 1 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_authorization_invalid_aws2 | FAIL | FAIL | FAIL | | 1 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_authorization_invalid_aws2 | FAIL | FAIL | FAIL |
| 2 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_ua_empty_aws2 | ERROR | ok | ok | | 2 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_ua_empty_aws2 | ERROR | ok | ok |
| 3 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_ua_none_aws2 | ERROR | ok | ok | | 3 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_ua_none_aws2 | ERROR | ok | ok |
| 4 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_date_invalid_aws2 | FAIL | FAIL | ok | | 4 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_date_invalid_aws2 | FAIL | FAIL | ok |
| 5 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_date_empty_aws2 | FAIL | FAIL | ok | | 5 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_date_empty_aws2 | FAIL | FAIL | ok |
| 6 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_date_none_aws2 | FAIL | FAIL | FAIL | | 6 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_date_none_aws2 | FAIL | FAIL | FAIL |
| 7 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_date_before_today_aws2 | FAIL | ok | ok | | 7 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_date_before_today_aws2 | FAIL | ok | ok |
| 8 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_date_after_today_aws2 | FAIL | ok | ok | | 8 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_date_after_today_aws2 | FAIL | ok | ok |
| 9 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_date_before_epoch_aws2 | FAIL | FAIL | ok | | 9 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_date_before_epoch_aws2 | FAIL | FAIL | ok |
| 10 | s3tests_boto3.functional.test_headers.test_bucket_create_contentlength_none | ok | ok | ok | | 10 | s3tests_boto3.functional.test_headers.test_bucket_create_contentlength_none | ok | ok | ok |
| 11 | s3tests_boto3.functional.test_headers.test_bucket_put_bad_canned_acl | ok | FAIL | ok | | 11 | s3tests_boto3.functional.test_headers.test_bucket_put_bad_canned_acl | ok | FAIL | ok |
| 12 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_expect_mismatch | ERROR | ERROR | ok | | 12 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_expect_mismatch | ERROR | ERROR | ok |
| 13 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_expect_empty | ok | ok | ok | | 13 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_expect_empty | ok | ok | ok |
| 14 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_contentlength_empty | FAIL | FAIL | ok | | 14 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_contentlength_empty | FAIL | FAIL | ok |
| 15 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_contentlength_negative | ok | ok | ok | | 15 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_contentlength_negative | ok | ok | ok |
| 16 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_contentlength_none | ok | ok | ok | | 16 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_contentlength_none | ok | ok | ok |
| 17 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_authorization_empty | FAIL | FAIL | FAIL | | 17 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_authorization_empty | FAIL | FAIL | FAIL |
| 18 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_authorization_none | FAIL | FAIL | FAIL | | 18 | s3tests_boto3.functional.test_headers.test_bucket_create_bad_authorization_none | FAIL | FAIL | FAIL |
| 19 | s3tests_boto3.functional.test_s3.test_bucket_notexist | ok | ok | ok | | 19 | s3tests_boto3.functional.test_s3.test_bucket_notexist | ok | ok | ok |
| 20 | s3tests_boto3.functional.test_s3.test_bucketv2_notexist | ok | ok | ok | | 20 | s3tests_boto3.functional.test_s3.test_bucketv2_notexist | ok | ok | ok |
| 21 | s3tests_boto3.functional.test_s3.test_bucket_delete_notexist | ok | ok | ok | | 21 | s3tests_boto3.functional.test_s3.test_bucket_delete_notexist | ok | ok | ok |
| 22 | s3tests_boto3.functional.test_s3.test_bucket_delete_nonempty | ok | ok | ok | | 22 | s3tests_boto3.functional.test_s3.test_bucket_delete_nonempty | ok | ok | ok |
| 23 | s3tests_boto3.functional.test_s3.test_bucket_concurrent_set_canned_acl | ok | FAIL | FAIL | | 23 | s3tests_boto3.functional.test_s3.test_bucket_concurrent_set_canned_acl | ok | FAIL | FAIL |
| 24 | s3tests_boto3.functional.test_s3.test_bucket_create_delete | ok | ok | ok | | 24 | s3tests_boto3.functional.test_s3.test_bucket_create_delete | ok | ok | ok |
| 25 | s3tests_boto3.functional.test_s3.test_bucket_head | ok | ok | ok | | 25 | s3tests_boto3.functional.test_s3.test_bucket_head | ok | ok | ok |
| 26 | s3tests_boto3.functional.test_s3.test_bucket_head_notexist | ok | ok | ok | | 26 | s3tests_boto3.functional.test_s3.test_bucket_head_notexist | ok | ok | ok |
| 27 | s3tests_boto3.functional.test_s3.test_bucket_head_extended | ERROR | ERROR | ERROR | | 27 | s3tests_boto3.functional.test_s3.test_bucket_head_extended | ERROR | ERROR | ERROR |
| 28 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_bad_starts_nonalpha | ok | ok | ok | | 28 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_bad_starts_nonalpha | ok | ok | ok |
| 29 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_bad_short_empty | ERROR | ERROR | ERROR | | 29 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_bad_short_empty | ERROR | ERROR | ERROR |
| 30 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_bad_short_one | ok | ok | ok | | 30 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_bad_short_one | ok | ok | ok |
| 31 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_bad_short_two | ok | ok | ok | | 31 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_bad_short_two | ok | ok | ok |
| 32 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_bad_long | ERROR | ERROR | ERROR | | 32 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_bad_long | ERROR | ERROR | ERROR |
| 33 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_good_long_60 | ok | ok | ok | | 33 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_good_long_60 | ok | ok | ok |
| 34 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_good_long_61 | ok | ok | ok | | 34 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_good_long_61 | ok | ok | ok |
| 35 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_good_long_62 | ok | ok | ok | | 35 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_good_long_62 | ok | ok | ok |
| 36 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_good_long_63 | ok | ok | ok | | 36 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_good_long_63 | ok | ok | ok |
| 37 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_bad_ip | ok | ok | FAIL | | 37 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_bad_ip | ok | ok | FAIL |
| 38 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_bad_punctuation | ERROR | ERROR | ERROR | | 38 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_bad_punctuation | ERROR | ERROR | ERROR |
| 39 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_dns_underscore | ok | ok | ok | | 39 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_dns_underscore | ok | ok | ok |
| 40 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_dns_long | ok | ok | ok | | 40 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_dns_long | ok | ok | ok |
| 41 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_dns_dash_at_end | ok | ok | ok | | 41 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_dns_dash_at_end | ok | ok | ok |
| 42 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_dns_dot_dot | ok | ok | ok | | 42 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_dns_dot_dot | ok | ok | ok |
| 43 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_dns_dot_dash | ok | ok | ok | | 43 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_dns_dot_dash | ok | ok | ok |
| 44 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_dns_dash_dot | ok | ok | ok | | 44 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_dns_dash_dot | ok | ok | ok |
| 45 | s3tests_boto3.functional.test_s3.test_bucket_create_exists | ERROR | ERROR | ok | | 45 | s3tests_boto3.functional.test_s3.test_bucket_create_exists | ERROR | ERROR | ok |
| 46 | s3tests_boto3.functional.test_s3.test_bucket_get_location | ok | FAIL | ERROR | | 46 | s3tests_boto3.functional.test_s3.test_bucket_get_location | ok | FAIL | ERROR |
| 47 | s3tests_boto3.functional.test_s3.test_bucket_create_exists_nonowner | ok | FAIL | ok | | 47 | s3tests_boto3.functional.test_s3.test_bucket_create_exists_nonowner | ok | FAIL | ok |
| 48 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_good_starts_alpha | ok | ok | ok | | 48 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_good_starts_alpha | ok | ok | ok |
| 49 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_good_starts_digit | ok | ok | ok | | 49 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_good_starts_digit | ok | ok | ok |
| 50 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_good_contains_period | ERROR | ok | ok | | 50 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_good_contains_period | ERROR | ok | ok |
| 51 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_good_contains_hyphen | ok | ok | ok | | 51 | s3tests_boto3.functional.test_s3.test_bucket_create_naming_good_contains_hyphen | ok | ok | ok |
| 52 | s3tests_boto3.functional.test_s3.test_bucket_recreate_not_overriding | ERROR | ERROR | ok | | 52 | s3tests_boto3.functional.test_s3.test_bucket_recreate_not_overriding | ERROR | ERROR | ok |
| 53 | s3tests_boto3.functional.test_s3.test_bucket_create_special_key_names | ok | ok | ok | | 53 | s3tests_boto3.functional.test_s3.test_bucket_create_special_key_names | ok | ok | ok |
| 54 | s3tests_boto3.functional.test_s3.test_bucket_policy_set_condition_operator_end_with_IfExists | ERROR | ERROR | FAIL | | 54 | s3tests_boto3.functional.test_s3.test_bucket_policy_set_condition_operator_end_with_IfExists | ERROR | ERROR | FAIL |
| 55 | s3tests_boto3.functional.test_s3.test_buckets_create_then_list | ok | ok | ok | | 55 | s3tests_boto3.functional.test_s3.test_buckets_create_then_list | ok | ok | ok |
| 56 | s3tests_boto3.functional.test_s3.test_buckets_list_ctime | ok | ok | FAIL | | 56 | s3tests_boto3.functional.test_s3.test_buckets_list_ctime | ok | ok | FAIL |
| 57 | s3tests_boto3.functional.test_s3.test_list_buckets_anonymous | ok | ERROR | ERROR | | 57 | s3tests_boto3.functional.test_s3.test_list_buckets_anonymous | ok | ERROR | ERROR |
| 58 | s3tests_boto3.functional.test_s3.test_list_buckets_invalid_auth | ok | ok | ok | | 58 | s3tests_boto3.functional.test_s3.test_list_buckets_invalid_auth | ok | ok | ok |
| 59 | s3tests_boto3.functional.test_s3.test_list_buckets_bad_auth | ok | ok | ok | | 59 | s3tests_boto3.functional.test_s3.test_list_buckets_bad_auth | ok | ok | ok |
## Bucket ACL ## Bucket ACL
@ -581,90 +581,90 @@ This group is not explicitly supported by s3-gw, but some other tests may pass.
Compatibility: 0/10/18 out of 29 Compatibility: 0/10/18 out of 29
This group is not explicitly supported by s3-gw, but some tests may pass. This group is not explicitly supported by s3-gw, but some tests may pass.
| | Test | s3-gw | minio | aws s3 | | | Test | s3-gw | minio | aws s3 |
|----|---------------------------------------------------------------------------------|-------|-------|--------| |-----|---------------------------------------------------------------------------------|-------|-------|--------|
| 1 | s3tests_boto3.functional.test_s3.test_lifecycle_set | ERROR | ok | ok | | 1 | s3tests_boto3.functional.test_s3.test_lifecycle_set | ERROR | ok | ok |
| 2 | s3tests_boto3.functional.test_s3.test_lifecycle_get | ERROR | FAIL | ok | | 2 | s3tests_boto3.functional.test_s3.test_lifecycle_get | ERROR | FAIL | ok |
| 3 | s3tests_boto3.functional.test_s3.test_lifecycle_get_no_id | ERROR | ERROR | ok | | 3 | s3tests_boto3.functional.test_s3.test_lifecycle_get_no_id | ERROR | ERROR | ok |
| 4 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration | ERROR | FAIL | FAIL | | 4 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration | ERROR | FAIL | FAIL |
| 5 | s3tests_boto3.functional.test_s3.test_lifecyclev2_expiration | ERROR | FAIL | FAIL | | 5 | s3tests_boto3.functional.test_s3.test_lifecyclev2_expiration | ERROR | FAIL | FAIL |
| 6 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_versioning_enabled | ERROR | ERROR | ok | | 6 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_versioning_enabled | ERROR | ERROR | ok |
| 7 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_tags1 | ERROR | ERROR | ERROR | | 7 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_tags1 | ERROR | ERROR | ERROR |
| 8 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_tags2 | ERROR | ERROR | ERROR | | 8 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_tags2 | ERROR | ERROR | ERROR |
| 9 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_versioned_tags2 | ERROR | ERROR | ERROR | | 9 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_versioned_tags2 | ERROR | ERROR | ERROR |
| 10 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_noncur_tags1 | ERROR | ERROR | ERROR | | 10 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_noncur_tags1 | ERROR | ERROR | ERROR |
| 11 | s3tests_boto3.functional.test_s3.test_lifecycle_id_too_long | FAIL | FAIL | ok | | 11 | s3tests_boto3.functional.test_s3.test_lifecycle_id_too_long | FAIL | FAIL | ok |
| 12 | s3tests_boto3.functional.test_s3.test_lifecycle_same_id | FAIL | FAIL | ok | | 12 | s3tests_boto3.functional.test_s3.test_lifecycle_same_id | FAIL | FAIL | ok |
| 13 | s3tests_boto3.functional.test_s3.test_lifecycle_invalid_status | FAIL | FAIL | ok | | 13 | s3tests_boto3.functional.test_s3.test_lifecycle_invalid_status | FAIL | FAIL | ok |
| 14 | s3tests_boto3.functional.test_s3.test_lifecycle_set_date | ERROR | ok | ok | | 14 | s3tests_boto3.functional.test_s3.test_lifecycle_set_date | ERROR | ok | ok |
| 15 | s3tests_boto3.functional.test_s3.test_lifecycle_set_invalid_date | FAIL | ok | ok | | 15 | s3tests_boto3.functional.test_s3.test_lifecycle_set_invalid_date | FAIL | ok | ok |
| 16 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_date | ERROR | FAIL | FAIL | | 16 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_date | ERROR | FAIL | FAIL |
| 17 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_days0 | FAIL | FAIL | ok | | 17 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_days0 | FAIL | FAIL | ok |
| 18 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_header_put | ERROR | ok | ok | | 18 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_header_put | ERROR | ok | ok |
| 19 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_header_head | ERROR | ok | ok | | 19 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_header_head | ERROR | ok | ok |
| 20 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_header_tags_head | ERROR | ok | FAIL | | 20 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_header_tags_head | ERROR | ok | FAIL |
| 21 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_header_and_tags_head | ERROR | ERROR | ok | | 21 | s3tests_boto3.functional.test_s3.test_lifecycle_expiration_header_and_tags_head | ERROR | ERROR | ok |
| 22 | s3tests_boto3.functional.test_s3.test_lifecycle_set_noncurrent | ERROR | ok | ok | | 22 | s3tests_boto3.functional.test_s3.test_lifecycle_set_noncurrent | ERROR | ok | ok |
| 23 | s3tests_boto3.functional.test_s3.test_lifecycle_noncur_expiration | ERROR | ERROR | FAIL | | 23 | s3tests_boto3.functional.test_s3.test_lifecycle_noncur_expiration | ERROR | ERROR | FAIL |
| 24 | s3tests_boto3.functional.test_s3.test_lifecycle_set_deletemarker | ERROR | ok | ok | | 24 | s3tests_boto3.functional.test_s3.test_lifecycle_set_deletemarker | ERROR | ok | ok |
| 25 | s3tests_boto3.functional.test_s3.test_lifecycle_set_filter | ERROR | ok | ok | | 25 | s3tests_boto3.functional.test_s3.test_lifecycle_set_filter | ERROR | ok | ok |
| 26 | s3tests_boto3.functional.test_s3.test_lifecycle_set_empty_filter | ERROR | ok | ok | | 26 | s3tests_boto3.functional.test_s3.test_lifecycle_set_empty_filter | ERROR | ok | ok |
| 27 | s3tests_boto3.functional.test_s3.test_lifecycle_deletemarker_expiration | ERROR | ERROR | FAIL | | 27 | s3tests_boto3.functional.test_s3.test_lifecycle_deletemarker_expiration | ERROR | ERROR | FAIL |
| 28 | s3tests_boto3.functional.test_s3.test_lifecycle_set_multipart | ERROR | ERROR | ok | | 28 | s3tests_boto3.functional.test_s3.test_lifecycle_set_multipart | ERROR | ERROR | ok |
| 29 | s3tests_boto3.functional.test_s3.test_lifecycle_multipart_expiration | ERROR | ERROR | FAIL | | 29 | s3tests_boto3.functional.test_s3.test_lifecycle_multipart_expiration | ERROR | ERROR | FAIL |
## Policy and replication ## Policy and replication
Compatibility: 0/7/20 out of 35 Compatibility: 0/7/20 out of 35
This group is not explicitly supported by s3-gw, but some tests may pass. This group is not explicitly supported by s3-gw, but some tests may pass.
| | Test | s3-gw | minio | aws s3 | | | Test | s3-gw | minio | aws s3 |
|----|-------------------------------------------------------------------------------------|-------|-------|--------| |-----|-------------------------------------------------------------------------------------|-------|-------|--------|
| 1 | s3tests_boto3.functional.test_s3.test_bucket_policy | ERROR | ok | ok | | 1 | s3tests_boto3.functional.test_s3.test_bucket_policy | ERROR | ok | ok |
| 2 | s3tests_boto3.functional.test_s3.test_bucketv2_policy | ERROR | ok | ok | | 2 | s3tests_boto3.functional.test_s3.test_bucketv2_policy | ERROR | ok | ok |
| 3 | s3tests_boto3.functional.test_s3.test_bucket_policy_acl | ERROR | ERROR | ok | | 3 | s3tests_boto3.functional.test_s3.test_bucket_policy_acl | ERROR | ERROR | ok |
| 4 | s3tests_boto3.functional.test_s3.test_bucketv2_policy_acl | ERROR | ERROR | ok | | 4 | s3tests_boto3.functional.test_s3.test_bucketv2_policy_acl | ERROR | ERROR | ok |
| 5 | s3tests_boto3.functional.test_s3.test_bucket_policy_different_tenant | ERROR | ERROR | ERROR | | 5 | s3tests_boto3.functional.test_s3.test_bucket_policy_different_tenant | ERROR | ERROR | ERROR |
| 6 | s3tests_boto3.functional.test_s3.test_bucketv2_policy_different_tenant | ERROR | ERROR | ERROR | | 6 | s3tests_boto3.functional.test_s3.test_bucketv2_policy_different_tenant | ERROR | ERROR | ERROR |
| 7 | s3tests_boto3.functional.test_s3.test_bucket_policy_another_bucket | ERROR | ok | ERROR | | 7 | s3tests_boto3.functional.test_s3.test_bucket_policy_another_bucket | ERROR | ok | ERROR |
| 8 | s3tests_boto3.functional.test_s3.test_bucketv2_policy_another_bucket | ERROR | ok | ERROR | | 8 | s3tests_boto3.functional.test_s3.test_bucketv2_policy_another_bucket | ERROR | ok | ERROR |
| 9 | s3tests_boto3.functional.test_s3.test_bucket_policy_get_obj_existing_tag | ERROR | ERROR | ok | | 9 | s3tests_boto3.functional.test_s3.test_bucket_policy_get_obj_existing_tag | ERROR | ERROR | ok |
| 10 | s3tests_boto3.functional.test_s3.test_bucket_policy_get_obj_tagging_existing_tag | ERROR | ERROR | ok | | 10 | s3tests_boto3.functional.test_s3.test_bucket_policy_get_obj_tagging_existing_tag | ERROR | ERROR | ok |
| 11 | s3tests_boto3.functional.test_s3.test_bucket_policy_put_obj_tagging_existing_tag | ERROR | ERROR | ok | | 11 | s3tests_boto3.functional.test_s3.test_bucket_policy_put_obj_tagging_existing_tag | ERROR | ERROR | ok |
| 12 | s3tests_boto3.functional.test_s3.test_bucket_policy_put_obj_copy_source | ERROR | FAIL | ok | | 12 | s3tests_boto3.functional.test_s3.test_bucket_policy_put_obj_copy_source | ERROR | FAIL | ok |
| 13 | s3tests_boto3.functional.test_s3.test_bucket_policy_put_obj_copy_source_meta | ERROR | FAIL | ok | | 13 | s3tests_boto3.functional.test_s3.test_bucket_policy_put_obj_copy_source_meta | ERROR | FAIL | ok |
| 14 | s3tests_boto3.functional.test_s3.test_bucket_policy_put_obj_acl | ERROR | ERROR | ok | | 14 | s3tests_boto3.functional.test_s3.test_bucket_policy_put_obj_acl | ERROR | ERROR | ok |
| 15 | s3tests_boto3.functional.test_s3.test_bucket_policy_put_obj_grant | ERROR | ERROR | ok | | 15 | s3tests_boto3.functional.test_s3.test_bucket_policy_put_obj_grant | ERROR | ERROR | ok |
| 16 | s3tests_boto3.functional.test_s3.test_bucket_policy_put_obj_enc | ERROR | FAIL | ERROR | | 16 | s3tests_boto3.functional.test_s3.test_bucket_policy_put_obj_enc | ERROR | FAIL | ERROR |
| 17 | s3tests_boto3.functional.test_s3.test_bucket_policy_put_obj_request_obj_tag | ERROR | ERROR | FAIL | | 17 | s3tests_boto3.functional.test_s3.test_bucket_policy_put_obj_request_obj_tag | ERROR | ERROR | FAIL |
| 18 | s3tests_boto3.functional.test_s3.test_bucket_policy_get_obj_acl_existing_tag | ERROR | ERROR | ok | | 18 | s3tests_boto3.functional.test_s3.test_bucket_policy_get_obj_acl_existing_tag | ERROR | ERROR | ok |
| 19 | s3tests_boto3.functional.test_s3.test_user_policy | ERROR | ERROR | ERROR | | 19 | s3tests_boto3.functional.test_s3.test_user_policy | ERROR | ERROR | ERROR |
| 20 | s3tests_boto3.functional.test_s3.test_get_bucket_policy_status | ERROR | ok | ERROR | | 20 | s3tests_boto3.functional.test_s3.test_get_bucket_policy_status | ERROR | ok | ERROR |
| 21 | s3tests_boto3.functional.test_s3.test_get_public_acl_bucket_policy_status | ERROR | ERROR | ERROR | | 21 | s3tests_boto3.functional.test_s3.test_get_public_acl_bucket_policy_status | ERROR | ERROR | ERROR |
| 22 | s3tests_boto3.functional.test_s3.test_get_authpublic_acl_bucket_policy_status | ERROR | ERROR | ERROR | | 22 | s3tests_boto3.functional.test_s3.test_get_authpublic_acl_bucket_policy_status | ERROR | ERROR | ERROR |
| 23 | s3tests_boto3.functional.test_s3.test_get_publicpolicy_acl_bucket_policy_status | ERROR | FAIL | ERROR | | 23 | s3tests_boto3.functional.test_s3.test_get_publicpolicy_acl_bucket_policy_status | ERROR | FAIL | ERROR |
| 24 | s3tests_boto3.functional.test_s3.test_get_nonpublicpolicy_acl_bucket_policy_status | ERROR | ok | ERROR | | 24 | s3tests_boto3.functional.test_s3.test_get_nonpublicpolicy_acl_bucket_policy_status | ERROR | ok | ERROR |
| 25 | s3tests_boto3.functional.test_s3.test_get_nonpublicpolicy_deny_bucket_policy_status | ERROR | ERROR | ERROR | | 25 | s3tests_boto3.functional.test_s3.test_get_nonpublicpolicy_deny_bucket_policy_status | ERROR | ERROR | ERROR |
| 26 | s3tests_boto3.functional.test_s3.test_get_default_public_block | ERROR | ERROR | ERROR | | 26 | s3tests_boto3.functional.test_s3.test_get_default_public_block | ERROR | ERROR | ERROR |
| 27 | s3tests_boto3.functional.test_s3.test_put_public_block | ERROR | ERROR | ok | | 27 | s3tests_boto3.functional.test_s3.test_put_public_block | ERROR | ERROR | ok |
| 28 | s3tests_boto3.functional.test_s3.test_block_public_put_bucket_acls | ERROR | ERROR | ok | | 28 | s3tests_boto3.functional.test_s3.test_block_public_put_bucket_acls | ERROR | ERROR | ok |
| 29 | s3tests_boto3.functional.test_s3.test_block_public_object_canned_acls | ERROR | ERROR | ok | | 29 | s3tests_boto3.functional.test_s3.test_block_public_object_canned_acls | ERROR | ERROR | ok |
| 30 | s3tests_boto3.functional.test_s3.test_block_public_policy | ERROR | ERROR | ok | | 30 | s3tests_boto3.functional.test_s3.test_block_public_policy | ERROR | ERROR | ok |
| 31 | s3tests_boto3.functional.test_s3.test_ignore_public_acls | ERROR | ERROR | FAIL | | 31 | s3tests_boto3.functional.test_s3.test_ignore_public_acls | ERROR | ERROR | FAIL |
| 32 | s3tests_boto3.functional.test_s3.test_get_tags_acl_public | ERROR | FAIL | ok | | 32 | s3tests_boto3.functional.test_s3.test_get_tags_acl_public | ERROR | FAIL | ok |
| 33 | s3tests_boto3.functional.test_s3.test_put_tags_acl_public | ERROR | FAIL | ok | | 33 | s3tests_boto3.functional.test_s3.test_put_tags_acl_public | ERROR | FAIL | ok |
| 34 | s3tests_boto3.functional.test_s3.test_delete_tags_obj_public | ERROR | ok | ok | | 34 | s3tests_boto3.functional.test_s3.test_delete_tags_obj_public | ERROR | ok | ok |
| 35 | s3tests_boto3.functional.test_s3.test_multipart_upload_on_a_bucket_with_policy | ERROR | ERROR | ok | | 35 | s3tests_boto3.functional.test_s3.test_multipart_upload_on_a_bucket_with_policy | ERROR | ERROR | ok |
## Others ## Others
Compatibility: 2/2/3 out of 6 Compatibility: 2/2/3 out of 6
| | Test | s3-gw | minio | aws s3 | | | Test | s3-gw | minio | aws s3 |
|---|-------------------------------------------------------------|-------|-------|--------| |-----|-------------------------------------------------------------|-------|-------|--------|
| 1 | s3tests_boto3.functional.test_s3.test_100_continue | FAIL | ERROR | ok | | 1 | s3tests_boto3.functional.test_s3.test_100_continue | FAIL | ERROR | ok |
| 2 | s3tests_boto3.functional.test_s3.test_account_usage | ERROR | ERROR | ERROR | | 2 | s3tests_boto3.functional.test_s3.test_account_usage | ERROR | ERROR | ERROR |
| 3 | s3tests_boto3.functional.test_s3.test_head_bucket_usage | ERROR | ERROR | ERROR | | 3 | s3tests_boto3.functional.test_s3.test_head_bucket_usage | ERROR | ERROR | ERROR |
| 4 | s3tests_boto3.functional.test_s3.test_logging_toggle | ERROR | ERROR | ERROR | | 4 | s3tests_boto3.functional.test_s3.test_logging_toggle | ERROR | ERROR | ERROR |
| 5 | s3tests_boto3.functional.test_s3.test_multi_object_delete | ok | ok | ok | | 5 | s3tests_boto3.functional.test_s3.test_multi_object_delete | ok | ok | ok |
| 6 | s3tests_boto3.functional.test_s3.test_multi_objectv2_delete | ok | ok | ok | | 6 | s3tests_boto3.functional.test_s3.test_multi_objectv2_delete | ok | ok | ok |

2
go.mod
View file

@ -5,7 +5,7 @@ go 1.18
require ( require (
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.11.2-0.20230315095236-9dc375346703 git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.11.2-0.20230315095236-9dc375346703
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20230316081442-bec77f280a85 git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20230329125804-552219b8e130
github.com/aws/aws-sdk-go v1.44.6 github.com/aws/aws-sdk-go v1.44.6
github.com/bluele/gcache v0.0.2 github.com/bluele/gcache v0.0.2
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0

4
go.sum
View file

@ -42,8 +42,8 @@ git.frostfs.info/TrueCloudLab/frostfs-contract v0.0.0-20230307110621-19a8ef2d02f
git.frostfs.info/TrueCloudLab/frostfs-contract v0.0.0-20230307110621-19a8ef2d02fb/go.mod h1:nkR5gaGeez3Zv2SE7aceP0YwxG2FzIB5cGKpQO2vV2o= 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 h1:FxqFDhQYYgpe41qsIHVOcdzSVCB8JNSfPG7Uk4r2oSk=
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0/go.mod h1:RUIKZATQLJ+TaYQa60X2fTDwfuhMfm8Ar60bQ5fr+vU= git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0/go.mod h1:RUIKZATQLJ+TaYQa60X2fTDwfuhMfm8Ar60bQ5fr+vU=
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20230316081442-bec77f280a85 h1:TUcJ5A0C1gWi3bAhw4b+V+iVM3E9mbBOdJIWWkAPNxo= 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-20230316081442-bec77f280a85/go.mod h1:23fUGlEv/ImaOi3vck6vZj0v0b4hteOhLLPnVWHSQeA= 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 h1:KvAES7xIqmQBGd2q8KanNosD9+4BhU/zqD5Kt5KSflk=
git.frostfs.info/TrueCloudLab/hrw v1.2.0/go.mod h1:mq2sbvYfO+BB6iFZwYBkgC0yc6mJNx+qZi4jW918m+Y= 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= git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 h1:M2KR3iBj7WpY3hP10IevfIB9MURr4O9mwVfJ+SjT3HA=

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,311 @@
package tree
import (
"context"
"errors"
"fmt"
"io"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/services/tree"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"google.golang.org/grpc"
)
type GetNodeByPathResponseInfoWrapper struct {
response *tree.GetNodeByPathResponse_Info
}
func (n GetNodeByPathResponseInfoWrapper) GetNodeID() uint64 {
return n.response.GetNodeId()
}
func (n GetNodeByPathResponseInfoWrapper) GetParentID() uint64 {
return n.response.GetParentId()
}
func (n GetNodeByPathResponseInfoWrapper) GetTimestamp() uint64 {
return n.response.GetTimestamp()
}
func (n GetNodeByPathResponseInfoWrapper) GetMeta() []Meta {
res := make([]Meta, len(n.response.Meta))
for i, value := range n.response.Meta {
res[i] = value
}
return res
}
type GetSubTreeResponseBodyWrapper struct {
response *tree.GetSubTreeResponse_Body
}
func (n GetSubTreeResponseBodyWrapper) GetNodeID() uint64 {
return n.response.GetNodeId()
}
func (n GetSubTreeResponseBodyWrapper) GetParentID() uint64 {
return n.response.GetParentId()
}
func (n GetSubTreeResponseBodyWrapper) GetTimestamp() uint64 {
return n.response.GetTimestamp()
}
func (n GetSubTreeResponseBodyWrapper) GetMeta() []Meta {
res := make([]Meta, len(n.response.Meta))
for i, value := range n.response.Meta {
res[i] = value
}
return res
}
type ServiceClientGRPC struct {
key *keys.PrivateKey
conn *grpc.ClientConn
service tree.TreeServiceClient
}
func NewTreeServiceClientGRPC(ctx context.Context, addr string, key *keys.PrivateKey, grpcOpts ...grpc.DialOption) (*ServiceClientGRPC, error) {
conn, err := grpc.Dial(addr, grpcOpts...)
if err != nil {
return nil, fmt.Errorf("did not connect: %v", err)
}
c := tree.NewTreeServiceClient(conn)
if _, err = c.Healthcheck(ctx, &tree.HealthcheckRequest{}); err != nil {
return nil, fmt.Errorf("healthcheck: %w", err)
}
return &ServiceClientGRPC{
key: key,
conn: conn,
service: c,
}, nil
}
func (c *ServiceClientGRPC) GetNodes(ctx context.Context, p *GetNodesParams) ([]NodeResponse, error) {
request := &tree.GetNodeByPathRequest{
Body: &tree.GetNodeByPathRequest_Body{
ContainerId: p.BktInfo.CID[:],
TreeId: p.TreeID,
Path: p.Path,
Attributes: p.Meta,
PathAttribute: FileNameKey,
LatestOnly: p.LatestOnly,
AllAttributes: p.AllAttrs,
BearerToken: getBearer(ctx, p.BktInfo),
},
}
if err := c.signRequest(request.Body, func(key, sign []byte) {
request.Signature = &tree.Signature{
Key: key,
Sign: sign,
}
}); err != nil {
return nil, err
}
resp, err := c.service.GetNodeByPath(ctx, request)
if err != nil {
return nil, handleError("failed to get node by path", err)
}
res := make([]NodeResponse, len(resp.GetBody().GetNodes()))
for i, info := range resp.GetBody().GetNodes() {
res[i] = GetNodeByPathResponseInfoWrapper{info}
}
return res, nil
}
func (c *ServiceClientGRPC) GetSubTree(ctx context.Context, bktInfo *data.BucketInfo, treeID string, rootID uint64, depth uint32) ([]NodeResponse, error) {
request := &tree.GetSubTreeRequest{
Body: &tree.GetSubTreeRequest_Body{
ContainerId: bktInfo.CID[:],
TreeId: treeID,
RootId: rootID,
Depth: depth,
BearerToken: getBearer(ctx, bktInfo),
},
}
if err := c.signRequest(request.Body, func(key, sign []byte) {
request.Signature = &tree.Signature{
Key: key,
Sign: sign,
}
}); err != nil {
return nil, err
}
cli, err := c.service.GetSubTree(ctx, request)
if err != nil {
return nil, handleError("failed to get sub tree client", err)
}
var subtree []NodeResponse
for {
resp, err := cli.Recv()
if err == io.EOF {
break
} else if err != nil {
return nil, handleError("failed to get sub tree", err)
}
subtree = append(subtree, GetSubTreeResponseBodyWrapper{resp.Body})
}
return subtree, nil
}
func (c *ServiceClientGRPC) AddNode(ctx context.Context, bktInfo *data.BucketInfo, treeID string, parent uint64, meta map[string]string) (uint64, error) {
request := &tree.AddRequest{
Body: &tree.AddRequest_Body{
ContainerId: bktInfo.CID[:],
TreeId: treeID,
ParentId: parent,
Meta: metaToKV(meta),
BearerToken: getBearer(ctx, bktInfo),
},
}
if err := c.signRequest(request.Body, func(key, sign []byte) {
request.Signature = &tree.Signature{
Key: key,
Sign: sign,
}
}); err != nil {
return 0, err
}
resp, err := c.service.Add(ctx, request)
if err != nil {
return 0, handleError("failed to add node", err)
}
return resp.GetBody().GetNodeId(), nil
}
func (c *ServiceClientGRPC) AddNodeByPath(ctx context.Context, bktInfo *data.BucketInfo, treeID string, path []string, meta map[string]string) (uint64, error) {
request := &tree.AddByPathRequest{
Body: &tree.AddByPathRequest_Body{
ContainerId: bktInfo.CID[:],
TreeId: treeID,
Path: path,
Meta: metaToKV(meta),
PathAttribute: FileNameKey,
BearerToken: getBearer(ctx, bktInfo),
},
}
if err := c.signRequest(request.Body, func(key, sign []byte) {
request.Signature = &tree.Signature{
Key: key,
Sign: sign,
}
}); err != nil {
return 0, err
}
resp, err := c.service.AddByPath(ctx, request)
if err != nil {
return 0, handleError("failed to add node by path", err)
}
body := resp.GetBody()
if body == nil {
return 0, errors.New("nil body in tree service response")
} else if len(body.Nodes) == 0 {
return 0, errors.New("empty list of added nodes in tree service response")
}
// The first node is the leaf that we add, according to tree service docs.
return body.Nodes[0], nil
}
func (c *ServiceClientGRPC) MoveNode(ctx context.Context, bktInfo *data.BucketInfo, treeID string, nodeID, parentID uint64, meta map[string]string) error {
request := &tree.MoveRequest{
Body: &tree.MoveRequest_Body{
ContainerId: bktInfo.CID[:],
TreeId: treeID,
NodeId: nodeID,
ParentId: parentID,
Meta: metaToKV(meta),
BearerToken: getBearer(ctx, bktInfo),
},
}
if err := c.signRequest(request.Body, func(key, sign []byte) {
request.Signature = &tree.Signature{
Key: key,
Sign: sign,
}
}); err != nil {
return err
}
if _, err := c.service.Move(ctx, request); err != nil {
return handleError("failed to move node", err)
}
return nil
}
func (c *ServiceClientGRPC) RemoveNode(ctx context.Context, bktInfo *data.BucketInfo, treeID string, nodeID uint64) error {
request := &tree.RemoveRequest{
Body: &tree.RemoveRequest_Body{
ContainerId: bktInfo.CID[:],
TreeId: treeID,
NodeId: nodeID,
BearerToken: getBearer(ctx, bktInfo),
},
}
if err := c.signRequest(request.Body, func(key, sign []byte) {
request.Signature = &tree.Signature{
Key: key,
Sign: sign,
}
}); err != nil {
return err
}
if _, err := c.service.Remove(ctx, request); err != nil {
return handleError("failed to remove node", err)
}
return nil
}
func metaToKV(meta map[string]string) []*tree.KeyValue {
result := make([]*tree.KeyValue, 0, len(meta))
for key, value := range meta {
result = append(result, &tree.KeyValue{Key: key, Value: []byte(value)})
}
return result
}
func getBearer(ctx context.Context, bktInfo *data.BucketInfo) []byte {
if bd, ok := ctx.Value(api.BoxData).(*accessbox.Box); ok && bd != nil && bd.Gate != nil {
if bd.Gate.BearerToken != nil {
if bktInfo.Owner.Equals(bearer.ResolveIssuer(*bd.Gate.BearerToken)) {
return bd.Gate.BearerToken.Marshal()
}
}
}
return nil
}
func handleError(msg string, err error) error {
if strings.Contains(err.Error(), "not found") {
return fmt.Errorf("%w: %s", ErrNodeNotFound, err.Error())
} else if strings.Contains(err.Error(), "is denied by") {
return fmt.Errorf("%w: %s", ErrNodeAccessDenied, err.Error())
}
return fmt.Errorf("%s: %w", msg, err)
}

View file

@ -1,12 +1,12 @@
/*REMOVE THIS AFTER SIGNATURE WILL BE AVAILABLE IN TREE CLIENT FROM FROSTFS NODE*/ /*REMOVE THIS AFTER SIGNATURE WILL BE AVAILABLE IN TREE CLIENT FROM FROSTFS NODE*/
package frostfs package tree
import ( import (
crypto "git.frostfs.info/TrueCloudLab/frostfs-crypto" crypto "git.frostfs.info/TrueCloudLab/frostfs-crypto"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
func (c *TreeClient) signData(buf []byte, f func(key, sign []byte)) error { func (c *ServiceClientGRPC) signData(buf []byte, f func(key, sign []byte)) error {
// crypto package should not be used outside of API libraries (see neofs-node#491). // crypto package should not be used outside of API libraries (see neofs-node#491).
// For now tree service does not include into SDK Client nor SDK Pool, so there is no choice. // For now tree service does not include into SDK Client nor SDK Pool, so there is no choice.
// When SDK library adopts Tree service client, this should be dropped. // When SDK library adopts Tree service client, this should be dropped.
@ -19,7 +19,7 @@ func (c *TreeClient) signData(buf []byte, f func(key, sign []byte)) error {
return nil return nil
} }
func (c *TreeClient) signRequest(requestBody proto.Message, f func(key, sign []byte)) error { func (c *ServiceClientGRPC) signRequest(requestBody proto.Message, f func(key, sign []byte)) error {
buf, err := proto.Marshal(requestBody) buf, err := proto.Marshal(requestBody)
if err != nil { if err != nil {
return err return err

View file

@ -0,0 +1,35 @@
package tree
import (
"errors"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
"github.com/stretchr/testify/require"
)
func TestHandleError(t *testing.T) {
defaultError := errors.New("default error")
for _, tc := range []struct {
err error
expectedError error
}{
{
err: defaultError,
expectedError: defaultError,
},
{
err: errors.New("something not found"),
expectedError: layer.ErrNodeNotFound,
},
{
err: errors.New("something is denied by some acl rule"),
expectedError: layer.ErrNodeAccessDenied,
},
} {
t.Run("", func(t *testing.T) {
err := handleError("err message", tc.err)
require.True(t, errors.Is(err, tc.expectedError))
})
}
}

View file

@ -0,0 +1,393 @@
package tree
import (
"context"
"fmt"
"sort"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
)
type nodeMeta struct {
key string
value []byte
}
func (m nodeMeta) GetKey() string {
return m.key
}
func (m nodeMeta) GetValue() []byte {
return m.value
}
type nodeResponse struct {
meta []nodeMeta
nodeID uint64
parentID uint64
timestamp uint64
}
func (n nodeResponse) GetNodeID() uint64 {
return n.nodeID
}
func (n nodeResponse) GetParentID() uint64 {
return n.parentID
}
func (n nodeResponse) GetTimestamp() uint64 {
return n.timestamp
}
func (n nodeResponse) GetMeta() []Meta {
res := make([]Meta, len(n.meta))
for i, value := range n.meta {
res[i] = value
}
return res
}
func (n nodeResponse) getValue(key string) string {
for _, value := range n.meta {
if value.key == key {
return string(value.value)
}
}
return ""
}
type ServiceClientMemory struct {
containers map[string]containerInfo
}
type containerInfo struct {
bkt *data.BucketInfo
trees map[string]memoryTree
}
type memoryTree struct {
idCounter uint64
treeData *treeNodeMemory
}
type treeNodeMemory struct {
data nodeResponse
parent *treeNodeMemory
children []*treeNodeMemory
}
func (t *treeNodeMemory) getNode(nodeID uint64) *treeNodeMemory {
if t.data.nodeID == nodeID {
return t
}
for _, child := range t.children {
if node := child.getNode(nodeID); node != nil {
return node
}
}
return nil
}
func (t *memoryTree) getNodesByPath(path []string) []nodeResponse {
if len(path) == 0 {
return nil
}
var res []nodeResponse
for _, child := range t.treeData.children {
res = child.listNodesByPath(res, path)
}
return res
}
func (t *treeNodeMemory) listNodesByPath(res []nodeResponse, path []string) []nodeResponse {
if len(path) == 0 || t.data.getValue(FileNameKey) != path[0] {
return res
}
if len(path) == 1 {
return append(res, t.data)
}
for _, ch := range t.children {
res = ch.listNodesByPath(res, path[1:])
}
return res
}
func (t *memoryTree) createPathIfNotExist(parent *treeNodeMemory, path []string) *treeNodeMemory {
if len(path) == 0 {
return parent
}
var node *treeNodeMemory
for _, child := range parent.children {
if len(child.data.meta) == 1 && child.data.getValue(FileNameKey) == path[0] {
node = child
break
}
}
if node == nil {
node = &treeNodeMemory{
data: nodeResponse{
meta: []nodeMeta{{key: FileNameKey, value: []byte(path[0])}},
nodeID: t.idCounter,
parentID: parent.data.nodeID,
timestamp: uint64(time.Now().UnixMicro()),
},
parent: parent,
}
t.idCounter++
parent.children = append(parent.children, node)
}
return t.createPathIfNotExist(node, path[1:])
}
func (t *treeNodeMemory) removeChild(nodeID uint64) {
ind := -1
for i, ch := range t.children {
if ch.data.nodeID == nodeID {
ind = i
break
}
}
if ind != -1 {
t.children = append(t.children[:ind], t.children[ind+1:]...)
}
}
func (t *treeNodeMemory) listNodes(res []NodeResponse, depth uint32) []NodeResponse {
res = append(res, t.data)
if depth == 0 {
return res
}
for _, ch := range t.children {
res = ch.listNodes(res, depth-1)
}
return res
}
func NewTreeServiceClientMemory() (*ServiceClientMemory, error) {
return &ServiceClientMemory{
containers: make(map[string]containerInfo),
}, nil
}
func (c *ServiceClientMemory) GetNodes(ctx context.Context, p *GetNodesParams) ([]NodeResponse, error) {
cnr, ok := c.containers[p.BktInfo.CID.EncodeToString()]
if !ok {
return nil, nil
}
tr, ok := cnr.trees[p.TreeID]
if !ok {
return nil, nil
}
res := tr.getNodesByPath(p.Path)
sort.Slice(res, func(i, j int) bool {
return res[i].timestamp < res[j].timestamp
})
if p.LatestOnly && len(res) != 0 {
res = res[len(res)-1:]
}
res2 := make([]NodeResponse, len(res))
for i, n := range res {
res2[i] = n
}
return res2, nil
}
func (c *ServiceClientMemory) GetSubTree(ctx context.Context, bktInfo *data.BucketInfo, treeID string, rootID uint64, depth uint32) ([]NodeResponse, error) {
cnr, ok := c.containers[bktInfo.CID.EncodeToString()]
if !ok {
return nil, nil
}
tr, ok := cnr.trees[treeID]
if !ok {
return nil, ErrNodeNotFound
}
node := tr.treeData.getNode(rootID)
if node == nil {
return nil, ErrNodeNotFound
}
return node.listNodes(nil, depth-1), nil
}
func newContainerInfo(bktInfo *data.BucketInfo, treeID string) containerInfo {
return containerInfo{
bkt: bktInfo,
trees: map[string]memoryTree{
treeID: {
idCounter: 1,
treeData: &treeNodeMemory{
data: nodeResponse{
timestamp: uint64(time.Now().UnixMicro()),
},
},
},
},
}
}
func newMemoryTree() memoryTree {
return memoryTree{
idCounter: 1,
treeData: &treeNodeMemory{
data: nodeResponse{
timestamp: uint64(time.Now().UnixMicro()),
},
},
}
}
func (c *ServiceClientMemory) AddNode(ctx context.Context, bktInfo *data.BucketInfo, treeID string, parent uint64, meta map[string]string) (uint64, error) {
cnr, ok := c.containers[bktInfo.CID.EncodeToString()]
if !ok {
cnr = newContainerInfo(bktInfo, treeID)
c.containers[bktInfo.CID.EncodeToString()] = cnr
}
tr, ok := cnr.trees[treeID]
if !ok {
tr = newMemoryTree()
cnr.trees[treeID] = tr
}
parentNode := tr.treeData.getNode(parent)
if parentNode == nil {
return 0, ErrNodeNotFound
}
newID := tr.idCounter
tr.idCounter++
tn := &treeNodeMemory{
data: nodeResponse{
meta: metaToNodeMeta(meta),
nodeID: newID,
parentID: parent,
timestamp: uint64(time.Now().UnixMicro()),
},
parent: parentNode,
}
parentNode.children = append(parentNode.children, tn)
cnr.trees[treeID] = tr
return newID, nil
}
func (c *ServiceClientMemory) AddNodeByPath(ctx context.Context, bktInfo *data.BucketInfo, treeID string, path []string, meta map[string]string) (uint64, error) {
cnr, ok := c.containers[bktInfo.CID.EncodeToString()]
if !ok {
cnr = newContainerInfo(bktInfo, treeID)
c.containers[bktInfo.CID.EncodeToString()] = cnr
}
tr, ok := cnr.trees[treeID]
if !ok {
tr = newMemoryTree()
cnr.trees[treeID] = tr
}
parentNode := tr.createPathIfNotExist(tr.treeData, path)
if parentNode == nil {
return 0, fmt.Errorf("create path '%s'", path)
}
newID := tr.idCounter
tr.idCounter++
tn := &treeNodeMemory{
data: nodeResponse{
meta: metaToNodeMeta(meta),
nodeID: newID,
parentID: parentNode.data.nodeID,
timestamp: uint64(time.Now().UnixMicro()),
},
parent: parentNode,
}
parentNode.children = append(parentNode.children, tn)
cnr.trees[treeID] = tr
return newID, nil
}
func (c *ServiceClientMemory) MoveNode(ctx context.Context, bktInfo *data.BucketInfo, treeID string, nodeID, parentID uint64, meta map[string]string) error {
cnr, ok := c.containers[bktInfo.CID.EncodeToString()]
if !ok {
return ErrNodeNotFound
}
tr, ok := cnr.trees[treeID]
if !ok {
return ErrNodeNotFound
}
node := tr.treeData.getNode(nodeID)
if node == nil {
return ErrNodeNotFound
}
newParent := tr.treeData.getNode(parentID)
if newParent == nil {
return ErrNodeNotFound
}
node.data.meta = metaToNodeMeta(meta)
node.data.parentID = parentID
newParent.children = append(newParent.children, node)
node.parent.removeChild(nodeID)
return nil
}
func (c *ServiceClientMemory) RemoveNode(ctx context.Context, bktInfo *data.BucketInfo, treeID string, nodeID uint64) error {
cnr, ok := c.containers[bktInfo.CID.EncodeToString()]
if !ok {
return ErrNodeNotFound
}
tr, ok := cnr.trees[treeID]
if !ok {
return ErrNodeNotFound
}
node := tr.treeData.getNode(nodeID)
if node == nil {
return ErrNodeNotFound
}
node.parent.removeChild(nodeID)
return nil
}
func metaToNodeMeta(m map[string]string) []nodeMeta {
result := make([]nodeMeta, 0, len(m))
for key, value := range m {
result = append(result, nodeMeta{key: key, value: []byte(value)})
}
return result
}

View file

@ -1,11 +1,12 @@
package frostfs package tree
import ( import (
"errors" "context"
"testing" "testing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer" cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -96,28 +97,73 @@ func TestLockConfigurationEncoding(t *testing.T) {
} }
} }
func TestHandleError(t *testing.T) { func TestTreeServiceSettings(t *testing.T) {
defaultError := errors.New("default error") ctx := context.Background()
for _, tc := range []struct {
err error memCli, err := NewTreeServiceClientMemory()
expectedError error require.NoError(t, err)
}{ treeService := NewTree(memCli)
{
err: defaultError, bktInfo := &data.BucketInfo{
expectedError: defaultError, CID: cidtest.ID(),
},
{
err: errors.New("something not found"),
expectedError: layer.ErrNodeNotFound,
},
{
err: errors.New("something is denied by some acl rule"),
expectedError: layer.ErrNodeAccessDenied,
},
} {
t.Run("", func(t *testing.T) {
err := handleError("err message", tc.err)
require.True(t, errors.Is(err, tc.expectedError))
})
} }
settings := &data.BucketSettings{
Versioning: "Versioning",
LockConfiguration: &data.ObjectLockConfiguration{
ObjectLockEnabled: "Enabled",
Rule: &data.ObjectLockRule{
DefaultRetention: &data.DefaultRetention{
Days: 1,
Mode: "mode",
},
},
},
}
err = treeService.PutSettingsNode(ctx, bktInfo, settings)
require.NoError(t, err)
storedSettings, err := treeService.GetSettingsNode(ctx, bktInfo)
require.NoError(t, err)
require.Equal(t, settings, storedSettings)
}
func TestTreeServiceAddVersion(t *testing.T) {
ctx := context.Background()
memCli, err := NewTreeServiceClientMemory()
require.NoError(t, err)
treeService := NewTree(memCli)
bktInfo := &data.BucketInfo{
CID: cidtest.ID(),
}
version := &data.NodeVersion{
BaseNodeVersion: data.BaseNodeVersion{
OID: oidtest.ID(),
Size: 10,
ETag: "etag",
FilePath: "path/to/version",
},
IsUnversioned: true,
}
nodeID, err := treeService.AddVersion(ctx, bktInfo, version)
require.NoError(t, err)
storedNode, err := treeService.GetUnversioned(ctx, bktInfo, "path/to/version")
require.NoError(t, err)
require.Equal(t, nodeID, storedNode.ID)
require.Equal(t, version.BaseNodeVersion.Size, storedNode.Size)
require.Equal(t, version.BaseNodeVersion.ETag, storedNode.ETag)
require.Equal(t, version.BaseNodeVersion.ETag, storedNode.ETag)
require.Equal(t, version.BaseNodeVersion.FilePath, storedNode.FilePath)
require.Equal(t, version.BaseNodeVersion.OID, storedNode.OID)
versions, err := treeService.GetVersions(ctx, bktInfo, "path/to/version")
require.NoError(t, err)
require.Len(t, versions, 1)
require.Equal(t, storedNode, versions[0])
} }

View file

@ -14,7 +14,7 @@ fi
RESULT_FILE=docs/s3_test_results.md RESULT_FILE=docs/s3_test_results.md
get_adjusted_result () { get_adjusted_result() {
local OLD_RESULT=$1 local OLD_RESULT=$1
local NEW_RESULT=$2 local NEW_RESULT=$2
local OLD_RESULT_LEN=${#OLD_RESULT} local OLD_RESULT_LEN=${#OLD_RESULT}
@ -23,13 +23,11 @@ get_adjusted_result () {
printf "%s%*s" "$NEW_RESULT" $ADDITIONAL_SPACES '' printf "%s%*s" "$NEW_RESULT" $ADDITIONAL_SPACES ''
} }
while read -r line; while read -r line; do
do
RES_LINE=$(echo "$line" | sed -nE '/^s3tests_boto3/p') RES_LINE=$(echo "$line" | sed -nE '/^s3tests_boto3/p')
if [ -n "$RES_LINE" ] if [ -n "$RES_LINE" ]; then
then TEST=${RES_LINE%%[[:space:]]*}
TEST=$(echo "$RES_LINE" | sed -e 's/[[:space:]]*\.\.\..*//') RESULT=${RES_LINE##*[[:space:]]}
RESULT=$(echo "$RES_LINE" | sed -e 's/^.*\.\.\.[[:space:]]*//')
# beautify trailing spaces # beautify trailing spaces
OLD_RESULT_S3GW=$(sed -n "s/^.*${TEST}[[:space:]]*|[[:space:]]\(.*\)[[:space:]]|.*|.*|$/\1/p" "$RESULT_FILE" | head -1) OLD_RESULT_S3GW=$(sed -n "s/^.*${TEST}[[:space:]]*|[[:space:]]\(.*\)[[:space:]]|.*|.*|$/\1/p" "$RESULT_FILE" | head -1)
@ -49,4 +47,4 @@ do
fi fi
fi fi
done < "$INPUT_FILE" done <"$INPUT_FILE"