Compare commits

..

47 commits

Author SHA1 Message Date
9edec7d573 [#641] Rework CORS bucket behaviour
All checks were successful
/ Vulncheck (push) Successful in 53s
/ Builds (push) Successful in 1m38s
/ OCI image (push) Successful in 2m24s
/ Lint (push) Successful in 3m0s
/ Tests (push) Successful in 1m16s
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
2025-03-11 08:22:30 +00:00
1fac8e3ef2 [#656] Don't ignore Expect header in sigv4
All checks were successful
/ DCO (pull_request) Successful in 53s
/ Vulncheck (pull_request) Successful in 1m14s
/ Builds (pull_request) Successful in 1m43s
/ OCI image (pull_request) Successful in 2m15s
/ Lint (pull_request) Successful in 3m17s
/ Tests (pull_request) Successful in 2m51s
/ Vulncheck (push) Successful in 1m5s
/ Builds (push) Successful in 54s
/ OCI image (push) Successful in 2m32s
/ Lint (push) Successful in 2m36s
/ Tests (push) Successful in 1m14s
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2025-03-10 12:01:18 +03:00
079fd20513 [#652] Port release v0.32.11
All checks were successful
/ DCO (pull_request) Successful in 42s
/ Vulncheck (pull_request) Successful in 1m7s
/ Builds (pull_request) Successful in 1m23s
/ OCI image (pull_request) Successful in 2m25s
/ Lint (pull_request) Successful in 2m59s
/ Tests (pull_request) Successful in 1m38s
/ Vulncheck (push) Successful in 1m2s
/ Builds (push) Successful in 1m0s
/ OCI image (push) Successful in 2m4s
/ Lint (push) Successful in 2m32s
/ Tests (push) Successful in 1m20s
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2025-02-28 14:15:20 +03:00
d597dd7c03 [#651] Update sdk
Update sdk to fix TrueCloudLab/frostfs-sdk-go#336

Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2025-02-28 14:12:48 +03:00
07b60b15b3 [#644] Support keepalive during listing
All checks were successful
/ DCO (pull_request) Successful in 32s
/ Vulncheck (pull_request) Successful in 1m7s
/ Builds (pull_request) Successful in 1m36s
/ Lint (pull_request) Successful in 2m0s
/ Tests (pull_request) Successful in 1m20s
/ OCI image (pull_request) Successful in 2m3s
/ Vulncheck (push) Successful in 1m1s
/ Builds (push) Successful in 1m2s
/ Lint (push) Successful in 2m14s
/ Tests (push) Successful in 1m28s
/ OCI image (push) Successful in 1m56s
Send whitespaces every time as new object in list is ready
to prevent client from context cancelling.

Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2025-02-27 09:37:04 +03:00
776fd042ef [#647] Dont send error after returning object payload
All checks were successful
/ Vulncheck (push) Successful in 1m0s
/ Builds (push) Successful in 1m2s
/ Lint (push) Successful in 2m12s
/ Tests (push) Successful in 1m18s
/ OCI image (push) Successful in 1m54s
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2025-02-25 10:01:58 +00:00
ffe91b43a1 [#648] fix: Prevent InternalError response in PostObject handler
All checks were successful
/ Vulncheck (push) Successful in 1m4s
/ Builds (push) Successful in 1m3s
/ OCI image (push) Successful in 2m6s
/ Lint (push) Successful in 2m9s
/ Tests (push) Successful in 1m18s
Signed-off-by: Nikita Zinkevich <n.zinkevich@yadro.com>
2025-02-25 09:46:54 +00:00
2c0a032966 [#648] fix: Pass tags during PostObject request
Signed-off-by: Nikita Zinkevich <n.zinkevich@yadro.com>
2025-02-25 09:46:54 +00:00
297199d885 [#637] Add IO tags
All checks were successful
/ DCO (pull_request) Successful in 29s
/ Vulncheck (pull_request) Successful in 44s
/ Builds (pull_request) Successful in 1m42s
/ OCI image (pull_request) Successful in 2m11s
/ Lint (pull_request) Successful in 2m29s
/ Tests (pull_request) Successful in 1m54s
/ Vulncheck (push) Successful in 46s
/ Builds (push) Successful in 1m3s
/ OCI image (push) Successful in 2m8s
/ Lint (push) Successful in 2m39s
/ Tests (push) Successful in 1m44s
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
2025-02-25 12:16:32 +03:00
0fba02aadb [#596] Use zaptest.Logger
All checks were successful
/ DCO (pull_request) Successful in 30s
/ Vulncheck (pull_request) Successful in 1m2s
/ Builds (pull_request) Successful in 1m19s
/ OCI image (pull_request) Successful in 2m4s
/ Lint (pull_request) Successful in 2m7s
/ Tests (pull_request) Successful in 1m17s
/ Vulncheck (push) Successful in 46s
/ Builds (push) Successful in 58s
/ OCI image (push) Successful in 1m59s
/ Lint (push) Successful in 2m12s
/ Tests (push) Successful in 1m39s
Use zaptest to get logs which get printed only if a test fails
or if you ran go test -v.

Dont use zaptest.Logger for fuzz otherwise ngfuzz/libfuzz crashes

Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2025-02-21 15:53:27 +03:00
f8852c7626 [#498] Update frostfs-observability version
All checks were successful
/ DCO (pull_request) Successful in 35s
/ Vulncheck (pull_request) Successful in 58s
/ Builds (pull_request) Successful in 1m46s
/ Lint (pull_request) Successful in 2m4s
/ Tests (pull_request) Successful in 1m17s
/ OCI image (pull_request) Successful in 2m9s
/ Vulncheck (push) Successful in 1m3s
/ Builds (push) Successful in 1m5s
/ Lint (push) Successful in 2m0s
/ Tests (push) Successful in 1m18s
/ OCI image (push) Successful in 2m9s
The new version of frostfs-observability has
improved the detail of tracing low-level rpc
calls by adding send and receive events.

Signed-off-by: Roman Loginov <r.loginov@yadro.com>
2025-02-21 11:25:11 +03:00
ac0140506c [#498] middleware: Add spans to detail the trace
Spans are added only to the following middleware:
* PolicyCheck
* Auth
* FrostfsIDValidation

This is done this way because these middleware are basic and
they interact with frostfs-storage.

Also, an explicit context has been added to many functions
so that the middleware spans do not include all subsequent spans.

Signed-off-by: Roman Loginov <r.loginov@yadro.com>
2025-02-21 11:25:08 +03:00
c2c062b778 [#498] frostfs: Add spans to detail the trace
Signed-off-by: Roman Loginov <r.loginov@yadro.com>
2025-02-18 18:28:23 +03:00
94af2770e5 [#498] tree: Add spans to detail the trace
Signed-off-by: Roman Loginov <r.loginov@yadro.com>
2025-02-18 18:14:22 +03:00
b5f0d0871c [#498] layer: Add spans to detail the trace
Signed-off-by: Roman Loginov <r.loginov@yadro.com>
2025-02-18 18:14:22 +03:00
4f0af5a0fd [#498] handler: Add spans to detail the trace
Signed-off-by: Roman Loginov <r.loginov@yadro.com>
2025-02-18 18:14:11 +03:00
bfec3e0a5e [#619] Fix content-length invalid check
All checks were successful
/ Vulncheck (push) Successful in 1m8s
/ Builds (push) Successful in 1m2s
/ OCI image (push) Successful in 2m7s
/ Lint (push) Successful in 2m7s
/ Tests (push) Successful in 1m19s
Signed-off-by: Roman Loginov <r.loginov@yadro.com>
2025-02-18 14:15:23 +00:00
711d6b2c71 [#642] Simplify tests
Some checks failed
/ DCO (pull_request) Successful in 35s
/ Vulncheck (pull_request) Successful in 52s
/ Builds (pull_request) Successful in 1m33s
/ OCI image (pull_request) Successful in 2m13s
/ Lint (pull_request) Successful in 2m16s
/ Tests (pull_request) Successful in 1m51s
/ Vulncheck (push) Successful in 1m8s
/ Builds (push) Successful in 1m3s
/ Tests (push) Successful in 1m17s
/ OCI image (push) Successful in 2m9s
/ Lint (push) Has been cancelled
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2025-02-17 09:44:41 +03:00
092567a5a0 Release v0.32.10
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2025-02-14 16:11:27 +03:00
e0a54fcbd3 [#642] Fix streaming empty body
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2025-02-13 16:55:27 +03:00
e184b333e4 [#612] Port changelog from support branch
All checks were successful
/ DCO (pull_request) Successful in 32s
/ Vulncheck (pull_request) Successful in 1m2s
/ Builds (pull_request) Successful in 1m29s
/ OCI image (pull_request) Successful in 1m58s
/ Lint (pull_request) Successful in 2m20s
/ Tests (pull_request) Successful in 1m17s
/ Vulncheck (push) Successful in 56s
/ Builds (push) Successful in 1m10s
/ OCI image (push) Successful in 2m0s
/ Lint (push) Successful in 2m27s
/ Tests (push) Successful in 1m22s
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
2025-02-12 14:50:09 +03:00
853036e44e [#612] Make Content-Md5 header check optional
All checks were successful
/ DCO (pull_request) Successful in 34s
/ Vulncheck (pull_request) Successful in 51s
/ Builds (pull_request) Successful in 1m38s
/ OCI image (pull_request) Successful in 2m19s
/ Lint (pull_request) Successful in 2m27s
/ Tests (pull_request) Successful in 1m53s
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
2025-02-12 14:29:19 +03:00
ee46382a68 [#606] Reorganize some log tags
Some checks failed
/ DCO (pull_request) Successful in 32s
/ Vulncheck (pull_request) Successful in 1m12s
/ Builds (pull_request) Successful in 58s
/ OCI image (pull_request) Successful in 2m4s
/ Lint (pull_request) Successful in 2m23s
/ Tests (pull_request) Successful in 1m13s
/ Vulncheck (push) Successful in 42s
/ Builds (push) Successful in 1m12s
/ Lint (push) Successful in 2m25s
/ Tests (push) Successful in 1m7s
/ OCI image (push) Failing after 11m37s
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2025-02-11 16:47:42 +03:00
b207eb48d9 [#606] Use all available log tags by default
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2025-02-11 16:47:36 +03:00
b7650e01ac [#606] Make log tags more explicit
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2025-02-11 16:47:30 +03:00
e7f620f137 [#606] Support log tagging
All checks were successful
/ DCO (pull_request) Successful in 28s
/ Vulncheck (pull_request) Successful in 1m6s
/ Builds (pull_request) Successful in 57s
/ OCI image (pull_request) Successful in 2m0s
/ Lint (pull_request) Successful in 3m19s
/ Tests (pull_request) Successful in 1m12s
Signed-off-by: Nikita Zinkevich <n.zinkevich@yadro.com>
2025-02-11 15:12:20 +03:00
ffac62e8b4 [#606] logs: Delete comments
Signed-off-by: Nikita Zinkevich <n.zinkevich@yadro.com>
2025-02-11 15:12:20 +03:00
182262ace2 [#636] Bump go version in vulncheck
All checks were successful
/ DCO (pull_request) Successful in 32s
/ Vulncheck (pull_request) Successful in 56s
/ Builds (pull_request) Successful in 1m6s
/ Lint (pull_request) Successful in 2m22s
/ Tests (pull_request) Successful in 1m15s
/ OCI image (pull_request) Successful in 2m31s
/ Builds (push) Successful in 1m3s
/ Vulncheck (push) Successful in 1m4s
/ OCI image (push) Successful in 2m11s
/ Lint (push) Successful in 3m11s
/ Tests (push) Successful in 1m14s
go1.23.5 triggers GO-2025-3447 but this is applicable
only for ppc64le platform.

Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2025-02-11 11:48:22 +03:00
893b506c83 [#626] Fix ALREADY REMOVED response status code
Some checks failed
/ DCO (pull_request) Successful in 30s
/ Vulncheck (pull_request) Failing after 1m15s
/ Builds (pull_request) Successful in 1m27s
/ OCI image (pull_request) Successful in 2m14s
/ Lint (pull_request) Successful in 3m7s
/ Tests (pull_request) Successful in 1m16s
/ Vulncheck (push) Failing after 1m25s
/ Builds (push) Successful in 58s
/ OCI image (push) Successful in 2m7s
/ Lint (push) Successful in 2m22s
/ Tests (push) Successful in 1m17s
Signed-off-by: Pavel Pogodaev <p.pogodaev@yadro.com>
2025-02-10 16:10:45 +03:00
beec37797d [#626] Fix ALREADY REMOVED response status code
Some checks failed
/ DCO (pull_request) Successful in 40s
/ Vulncheck (pull_request) Failing after 1m45s
/ Builds (pull_request) Successful in 2m4s
/ OCI image (pull_request) Successful in 3m3s
/ Lint (pull_request) Successful in 3m21s
/ Tests (pull_request) Successful in 1m24s
Signed-off-by: Pavel Pogodaev <p.pogodaev@yadro.com>
2025-02-07 17:37:40 +03:00
5538dce772 [#628] Add tree_stream_timeout config parameter
Some checks failed
/ DCO (pull_request) Successful in 36s
/ Vulncheck (pull_request) Failing after 58s
/ Builds (pull_request) Successful in 1m44s
/ Lint (pull_request) Successful in 2m17s
/ Tests (pull_request) Successful in 2m11s
/ OCI image (pull_request) Successful in 2m40s
/ Vulncheck (push) Failing after 1m7s
/ Builds (push) Successful in 1m9s
/ OCI image (push) Successful in 2m10s
/ Lint (push) Successful in 3m15s
/ Tests (push) Successful in 1m15s
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
2025-02-07 11:57:54 +03:00
da9703ab63 [#623] Fix using copy numbers during multipart
All checks were successful
/ DCO (pull_request) Successful in 34s
/ Vulncheck (pull_request) Successful in 1m2s
/ Builds (pull_request) Successful in 1m12s
/ OCI image (pull_request) Successful in 2m1s
/ Lint (pull_request) Successful in 2m24s
/ Tests (pull_request) Successful in 1m22s
/ Vulncheck (push) Successful in 1m3s
/ Builds (push) Successful in 1m17s
/ Lint (push) Successful in 2m17s
/ Tests (push) Successful in 1m22s
/ OCI image (push) Successful in 2m36s
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2025-02-06 14:41:27 +03:00
a53e50b324 [#607] Support sigV4a streaming with trailers
All checks were successful
/ DCO (pull_request) Successful in 32s
/ Vulncheck (pull_request) Successful in 1m6s
/ Builds (pull_request) Successful in 1m34s
/ OCI image (pull_request) Successful in 2m2s
/ Lint (pull_request) Successful in 2m10s
/ Tests (pull_request) Successful in 1m17s
/ Vulncheck (push) Successful in 1m6s
/ Builds (push) Successful in 1m6s
/ Lint (push) Successful in 1m57s
/ Tests (push) Successful in 1m20s
/ OCI image (push) Successful in 2m10s
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2025-02-03 18:25:15 +03:00
5e9c562683 [#607] Fix aws example test for trailing with sigv4
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2025-02-03 18:25:15 +03:00
49bf3c1bce [#607] Support sigV4 streaming with trailers
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2025-02-03 18:25:15 +03:00
a4d9658fbb [#607] Support unsigned payload streaming with trailers
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2025-02-03 18:25:15 +03:00
bec63026bd [#607] Support unsigned payload streaming
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2025-02-03 18:25:15 +03:00
0064e7ab07 [#618] Port changelog from support branch
All checks were successful
/ Vulncheck (push) Successful in 58s
/ Builds (push) Successful in 1m17s
/ OCI image (push) Successful in 2m10s
/ Lint (push) Successful in 2m21s
/ Tests (push) Successful in 1m46s
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2025-02-03 14:27:09 +00:00
41e1f1ad7a [#617] Bump SDK version to the latest master
Contains fixes:
- memory leak in gRPC client,
- panic and deadlock in tree pool.

Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2025-02-03 14:27:09 +00:00
4d2e6f8650 [#610] Fix updateServers finding logic
All checks were successful
/ Vulncheck (push) Successful in 1m24s
/ Builds (push) Successful in 1m55s
/ OCI image (push) Successful in 2m36s
/ Lint (push) Successful in 2m41s
/ Tests (push) Successful in 2m2s
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2025-02-03 13:00:15 +00:00
3d3dd00211 [#615] Use UNSIGNED_PAYLOAD to check sign
All checks were successful
/ Vulncheck (push) Successful in 1m4s
/ Lint (push) Successful in 1m53s
/ Tests (push) Successful in 1m17s
/ OCI image (push) Successful in 2m7s
/ Builds (push) Successful in 1m2s
Use `UNSIGNED_PAYLOAD` to check signature if x-amz-content-sha256 isn't provided as signed header

https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html

" You include the literal string UNSIGNED-PAYLOAD when constructing a canonical request, and set the same value as the x-amz-content-sha256 header value when sending the request to Amazon S3"

Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2025-01-30 13:16:40 +00:00
510b0a1005 [#614] govulncheck: Use patch release with security fixes
All checks were successful
/ Vulncheck (push) Successful in 48s
/ Builds (push) Successful in 1m49s
/ OCI image (push) Successful in 2m28s
/ Lint (push) Successful in 3m3s
/ Tests (push) Successful in 1m48s
https://go.dev/doc/devel/release#go1.23.minor

Signed-off-by: Vitaliy Potyarkin <v.potyarkin@yadro.com>
2025-01-29 12:07:28 +00:00
da77e426b6 [#541] Fix setting of tls.enabled flag
Some checks failed
/ Builds (push) Has been cancelled
/ OCI image (push) Has been cancelled
/ Lint (push) Has been cancelled
/ Tests (push) Has been cancelled
/ Vulncheck (push) Has been cancelled
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
2025-01-29 12:06:41 +00:00
e7a8d4bdaf [#605] Fix panic when payload discard
Some checks failed
/ DCO (pull_request) Successful in 30s
/ Vulncheck (pull_request) Successful in 1m7s
/ Builds (pull_request) Successful in 1m31s
/ Lint (pull_request) Successful in 2m4s
/ Tests (pull_request) Successful in 1m8s
/ OCI image (pull_request) Successful in 2m10s
/ Builds (push) Has been cancelled
/ OCI image (push) Has been cancelled
/ Lint (push) Has been cancelled
/ Vulncheck (push) Has been cancelled
/ Tests (push) Has been cancelled
Signed-off-by: Roman Loginov <r.loginov@yadro.com>
2025-01-27 17:01:50 +03:00
250538a9b4 [#541] Use default value if config param is unset after SIGHUP
All checks were successful
/ DCO (pull_request) Successful in 31s
/ Vulncheck (pull_request) Successful in 1m2s
/ Builds (pull_request) Successful in 1m19s
/ Lint (pull_request) Successful in 2m6s
/ Tests (pull_request) Successful in 1m17s
/ OCI image (pull_request) Successful in 2m6s
/ Vulncheck (push) Successful in 1m4s
/ Builds (push) Successful in 1m6s
/ OCI image (push) Successful in 2m6s
/ Lint (push) Successful in 2m6s
/ Tests (push) Successful in 1m15s
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
2025-01-23 09:52:48 +03:00
619385836d [#585] Add ListBuckets handler test
All checks were successful
/ Vulncheck (push) Successful in 1m2s
/ Builds (push) Successful in 1m5s
/ OCI image (push) Successful in 1m55s
/ Lint (push) Successful in 2m15s
/ Tests (push) Successful in 1m17s
Modify containers field in TestFrostFS in order to get determined order of containers between test runs

Signed-off-by: Nikita Zinkevich <n.zinkevich@yadro.com>
2025-01-21 07:49:19 +00:00
65fc776dea [#585] Add ListBuckets pagination
Signed-off-by: Nikita Zinkevich <n.zinkevich@yadro.com>
2025-01-21 07:49:19 +00:00
99 changed files with 3321 additions and 1318 deletions

View file

@ -4,11 +4,6 @@ This document outlines major changes between releases.
## [Unreleased]
## [0.32.12] - 2025-03-07
### Fixed
- Reduced number of dial calls to available unhealthy tree services (#654)
## [0.32.11] - 2025-02-28
### Fixed
@ -469,5 +464,4 @@ To see CHANGELOG for older versions, refer to https://github.com/nspcc-dev/neofs
[0.32.9]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.32.8...v0.32.9
[0.32.10]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.32.9...v0.32.10
[0.32.11]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.32.10...v0.32.11
[0.32.12]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.32.11...v0.32.12
[Unreleased]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.32.12...master
[Unreleased]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.32.11...master

View file

@ -1 +1 @@
v0.32.12
v0.32.11

View file

@ -33,13 +33,16 @@ var (
// AuthorizationFieldRegexp -- is regexp for credentials with Base58 encoded cid and oid and '0' (zero) as delimiter.
AuthorizationFieldRegexp = regexp.MustCompile(`AWS4-HMAC-SHA256 Credential=(?P<access_key_id>[^/]+)/(?P<date>[^/]+)/(?P<region>[^/]*)/(?P<service>[^/]+)/aws4_request,\s*SignedHeaders=(?P<signed_header_fields>.+),\s*Signature=(?P<v4_signature>.+)`)
// authorizationFieldV4aRegexp -- is regexp for credentials with Base58 encoded cid and oid and '0' (zero) as delimiter.
authorizationFieldV4aRegexp = regexp.MustCompile(`AWS4-ECDSA-P256-SHA256 Credential=(?P<access_key_id>[^/]+)/(?P<date>[^/]+)/(?P<service>[^/]+)/aws4_request,\s*SignedHeaders=(?P<signed_header_fields>.+),\s*Signature=(?P<v4_signature>.+)`)
// AuthorizationFieldV4aRegexp -- is regexp for credentials with Base58 encoded cid and oid and '0' (zero) as delimiter.
AuthorizationFieldV4aRegexp = regexp.MustCompile(`AWS4-ECDSA-P256-SHA256 Credential=(?P<access_key_id>[^/]+)/(?P<date>[^/]+)/(?P<service>[^/]+)/aws4_request,\s*SignedHeaders=(?P<signed_header_fields>.+),\s*Signature=(?P<v4_signature>.+)`)
// postPolicyCredentialRegexp -- is regexp for credentials when uploading file using POST with policy.
postPolicyCredentialRegexp = regexp.MustCompile(`(?P<access_key_id>[^/]+)/(?P<date>[^/]+)/(?P<region>[^/]*)/(?P<service>[^/]+)/aws4_request`)
)
// need for tests.
var timeNow = time.Now
type (
Center struct {
reg *RegexpSubmatcher
@ -107,7 +110,7 @@ func New(creds tokens.Credentials, prefixes []string, settings CenterSettings) *
return &Center{
cli: creds,
reg: NewRegexpMatcher(AuthorizationFieldRegexp),
regV4a: NewRegexpMatcher(authorizationFieldV4aRegexp),
regV4a: NewRegexpMatcher(AuthorizationFieldV4aRegexp),
postReg: NewRegexpMatcher(postPolicyCredentialRegexp),
allowedAccessKeyIDPrefixes: prefixes,
settings: settings,
@ -115,8 +118,8 @@ func New(creds tokens.Credentials, prefixes []string, settings CenterSettings) *
}
const (
signaturePreambleSigV4 = "AWS4-HMAC-SHA256"
signaturePreambleSigV4A = "AWS4-ECDSA-P256-SHA256"
SignaturePreambleSigV4 = "AWS4-HMAC-SHA256"
SignaturePreambleSigV4A = "AWS4-ECDSA-P256-SHA256"
)
func (c *Center) parseAuthHeader(authHeader string, headers http.Header) (*AuthHeader, error) {
@ -128,13 +131,13 @@ func (c *Center) parseAuthHeader(authHeader string, headers http.Header) (*AuthH
)
switch preamble {
case signaturePreambleSigV4:
case SignaturePreambleSigV4:
submatches = c.reg.GetSubmatches(authHeader)
if len(submatches) != authHeaderPartsNum {
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrAuthorizationHeaderMalformed), authHeader)
}
region = submatches["region"]
case signaturePreambleSigV4A:
case SignaturePreambleSigV4A:
submatches = c.regV4a.GetSubmatches(authHeader)
if len(submatches) != authHeaderV4aPartsNum {
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrAuthorizationHeaderMalformed), authHeader)
@ -161,7 +164,7 @@ func IsStandardContentSHA256(key string) bool {
return ok
}
func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) {
func (c *Center) Authenticate(ctx context.Context, r *http.Request) (*middleware.Box, error) {
var (
err error
authHdr *AuthHeader
@ -170,7 +173,7 @@ func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) {
)
queryValues := r.URL.Query()
if queryValues.Get(AmzAlgorithm) == signaturePreambleSigV4 {
if queryValues.Get(AmzAlgorithm) == SignaturePreambleSigV4 {
creds := strings.Split(queryValues.Get(AmzCredential), "/")
if len(creds) != 5 || creds[4] != "aws4_request" {
return nil, fmt.Errorf("bad X-Amz-Credential")
@ -183,7 +186,7 @@ func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) {
SignedFields: strings.Split(queryValues.Get(AmzSignedHeaders), ";"),
Date: creds[1],
IsPresigned: true,
Preamble: signaturePreambleSigV4,
Preamble: SignaturePreambleSigV4,
PayloadHash: r.Header.Get(AmzContentSHA256),
}
authHdr.Expiration, err = time.ParseDuration(queryValues.Get(AmzExpires) + "s")
@ -191,7 +194,7 @@ func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) {
return nil, fmt.Errorf("%w: couldn't parse X-Amz-Expires %v", apierr.GetAPIError(apierr.ErrMalformedExpires), err)
}
signatureDateTimeStr = queryValues.Get(AmzDate)
} else if queryValues.Get(AmzAlgorithm) == signaturePreambleSigV4A {
} else if queryValues.Get(AmzAlgorithm) == SignaturePreambleSigV4A {
creds := strings.Split(queryValues.Get(AmzCredential), "/")
if len(creds) != 4 || creds[3] != "aws4_request" {
return nil, fmt.Errorf("bad X-Amz-Credential")
@ -204,7 +207,7 @@ func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) {
SignedFields: strings.Split(queryValues.Get(AmzSignedHeaders), ";"),
Date: creds[1],
IsPresigned: true,
Preamble: signaturePreambleSigV4A,
Preamble: SignaturePreambleSigV4A,
PayloadHash: r.Header.Get(AmzContentSHA256),
}
authHdr.Expiration, err = time.ParseDuration(queryValues.Get(AmzExpires) + "s")
@ -216,7 +219,7 @@ func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) {
authHeaderField := r.Header[AuthorizationHdr]
if len(authHeaderField) != 1 {
if strings.HasPrefix(r.Header.Get(ContentTypeHdr), "multipart/form-data") {
return c.checkFormData(r)
return c.checkFormData(ctx, r)
}
return nil, fmt.Errorf("%w: %v", middleware.ErrNoAuthorizationHeader, authHeaderField)
}
@ -242,7 +245,7 @@ func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) {
return nil, err
}
box, attrs, err := c.cli.GetBox(r.Context(), cnrID, authHdr.AccessKeyID)
box, attrs, err := c.cli.GetBox(ctx, cnrID, authHdr.AccessKeyID)
if err != nil {
return nil, fmt.Errorf("get box by access key '%s': %w", authHdr.AccessKeyID, err)
}
@ -315,7 +318,7 @@ func (c Center) checkAccessKeyID(accessKeyID string) error {
return fmt.Errorf("%w: accesskeyID prefix isn't allowed", apierr.GetAPIError(apierr.ErrAccessDenied))
}
func (c *Center) checkFormData(r *http.Request) (*middleware.Box, error) {
func (c *Center) checkFormData(ctx context.Context, r *http.Request) (*middleware.Box, error) {
if err := r.ParseMultipartForm(maxFormSizeMemory); err != nil {
return nil, fmt.Errorf("%w: parse multipart form with max size %d", apierr.GetAPIError(apierr.ErrInvalidArgument), maxFormSizeMemory)
}
@ -347,7 +350,7 @@ func (c *Center) checkFormData(r *http.Request) (*middleware.Box, error) {
return nil, err
}
box, attrs, err := c.cli.GetBox(r.Context(), cnrID, accessKeyID)
box, attrs, err := c.cli.GetBox(ctx, cnrID, accessKeyID)
if err != nil {
return nil, fmt.Errorf("get box by accessKeyID '%s': %w", accessKeyID, err)
}
@ -402,7 +405,7 @@ func (c *Center) checkSign(ctx context.Context, authHeader *AuthHeader, box *acc
}
switch authHeader.Preamble {
case signaturePreambleSigV4:
case SignaturePreambleSigV4:
creds := aws.Credentials{
AccessKeyID: authHeader.AccessKeyID,
SecretAccessKey: box.Gate.SecretKey,
@ -437,7 +440,7 @@ func (c *Center) checkSign(ctx context.Context, authHeader *AuthHeader, box *acc
authHeader.Signature, signature, authHeader.SignedFields)
}
case signaturePreambleSigV4A:
case SignaturePreambleSigV4A:
signer := v4a.NewSigner(func(options *v4a.SignerOptions) {
options.DisableURIPathEscaping = true
})
@ -470,7 +473,7 @@ func (c *Center) checkSign(ctx context.Context, authHeader *AuthHeader, box *acc
}
func checkPresignedDate(authHeader *AuthHeader, signatureDateTime time.Time) error {
now := time.Now()
now := timeNow()
if signatureDateTime.Add(authHeader.Expiration).Before(now) {
return fmt.Errorf("%w: expired: now %s, signature %s", apierr.GetAPIError(apierr.ErrExpiredPresignRequest),
now.Format(time.RFC3339), signatureDateTime.Format(time.RFC3339))

View file

@ -69,7 +69,7 @@ func TestAuthHeaderParse(t *testing.T) {
Signature: "2811ccb9e242f41426738fb1f",
SignedFields: []string{"host", "x-amz-content-sha256", "x-amz-date"},
Date: "20210809",
Preamble: signaturePreambleSigV4,
Preamble: SignaturePreambleSigV4,
},
},
{
@ -182,6 +182,71 @@ func TestSignatureV4(t *testing.T) {
require.Equal(t, signature, signatureComputed, "signature mismatched")
}
func TestSignExpectHeader(t *testing.T) {
timeNow = func() time.Time {
return time.Date(2025, 3, 10, 11, 0, 0, 0, time.UTC)
}
t.Cleanup(func() {
timeNow = time.Now
})
ctx := context.Background()
key, err := keys.NewPrivateKey()
require.NoError(t, err)
cfg := &cache.Config{
Size: 10,
Lifetime: 24 * time.Hour,
Logger: zaptest.NewLogger(t),
}
gateData := []*accessbox.GateData{{
BearerToken: &bearer.Token{},
GateKey: key.PublicKey(),
}}
accessBox, _, err := accessbox.PackTokens(gateData, []byte("0a85c05b993e7663bdd245aa780569ee8abbdc1d9d9a9e185395ff87dffb0105"), true)
require.NoError(t, err)
data, err := accessBox.Marshal()
require.NoError(t, err)
var obj object.Object
obj.SetPayload(data)
var addr oid.Address
err = addr.DecodeString("2t6v52sJdxN2NV2bFfYnuTSQiYkbcDQ2yMKHfGSsjHiv/HbUvXKFK7meCQWoo1Es3oGSMQFbFK7Np82JVEMbCM48d")
require.NoError(t, err)
frostfs := newFrostFSMock()
frostfs.objects[getAccessKeyID(addr)] = &obj
tknCfg := tokens.Config{
FrostFS: frostfs,
Key: key,
CacheConfig: cfg,
}
creds := tokens.New(tknCfg)
cntr := New(creds, nil, &centerSettingsMock{})
body := bytes.NewBufferString("")
req, err := http.NewRequest("PUT", "http://localhost:8184/test/tmp.txt", body)
require.NoError(t, err)
req.Header.Set("Authorization", "AWS4-HMAC-SHA256 Credential=2t6v52sJdxN2NV2bFfYnuTSQiYkbcDQ2yMKHfGSsjHiv0HbUvXKFK7meCQWoo1Es3oGSMQFbFK7Np82JVEMbCM48d/20250310/us-east-2/s3/aws4_request, SignedHeaders=expect;host;x-amz-content-sha256;x-amz-date, Signature=966bda3d22213ae3f10d82b302041d2fc1c18804e8c02ca7d9bd35b6bcb8c161")
req.Header.Set("Expect", "100-continue")
req.Header.Set("X-Amz-Content-Sha256", UnsignedPayload)
req.Header.Set("X-Amz-Date", "20250310T082808Z")
_, err = cntr.Authenticate(ctx, req)
require.NoError(t, err)
req2, err := http.NewRequest("PUT", "http://localhost:8184/test/tmp.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=2t6v52sJdxN2NV2bFfYnuTSQiYkbcDQ2yMKHfGSsjHiv0HbUvXKFK7meCQWoo1Es3oGSMQFbFK7Np82JVEMbCM48d%2F20250310%2Fru%2Fs3%2Faws4_request&X-Amz-Date=20250310T083436Z&X-Amz-Expires=43200&X-Amz-SignedHeaders=expect%3Bhost&X-Amz-Signature=42c3dbe45842fdfd62d78632e135f5460e3f709ab6b949e257de30168946859b", body)
require.NoError(t, err)
req2.Header.Set("Expect", "100-continue")
_, err = cntr.Authenticate(ctx, req2)
require.NoError(t, err)
}
func TestCheckFormatContentSHA256(t *testing.T) {
defaultErr := errors.GetAPIError(errors.ErrContentSHA256Mismatch)
@ -567,7 +632,7 @@ func TestAuthenticate(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
creds := tokens.New(bigConfig)
cntr := New(creds, tc.prefixes, &centerSettingsMock{})
box, err := cntr.Authenticate(tc.request)
box, err := cntr.Authenticate(ctx, tc.request)
if tc.err {
require.Error(t, err)
@ -600,6 +665,7 @@ func TestHTTPPostAuthenticate(t *testing.T) {
region = "default"
)
ctx := context.Background()
key, err := keys.NewPrivateKey()
require.NoError(t, err)
@ -751,7 +817,7 @@ func TestHTTPPostAuthenticate(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
creds := tokens.New(bigConfig)
cntr := New(creds, tc.prefixes, &centerSettingsMock{})
box, err := cntr.Authenticate(tc.request)
box, err := cntr.Authenticate(ctx, tc.request)
if tc.err {
require.Error(t, err)

View file

@ -99,12 +99,14 @@ func TestCheckSign(t *testing.T) {
postReg: NewRegexpMatcher(postPolicyCredentialRegexp),
settings: &centerSettingsMock{},
}
box, err := c.Authenticate(req)
box, err := c.Authenticate(ctx, req)
require.NoError(t, err)
require.EqualValues(t, expBox, box.AccessBox)
}
func TestCheckSignV4a(t *testing.T) {
ctx := context.Background()
var accessKeyAddr oid.Address
err := accessKeyAddr.DecodeString("8N7CYBY74kxZXoyvA5UNdmovaXqFpwNfvEPsqaN81es2/3tDwq5tR8fByrJcyJwyiuYX7Dae8tyDT7pd8oaL1MBto")
require.NoError(t, err)
@ -145,10 +147,10 @@ func TestCheckSignV4a(t *testing.T) {
c := &Center{
cli: mock,
regV4a: NewRegexpMatcher(authorizationFieldV4aRegexp),
regV4a: NewRegexpMatcher(AuthorizationFieldV4aRegexp),
postReg: NewRegexpMatcher(postPolicyCredentialRegexp),
}
box, err := c.Authenticate(req)
box, err := c.Authenticate(ctx, req)
require.NoError(t, err)
require.EqualValues(t, expBox, box.AccessBox)
}

View file

@ -1,6 +1,7 @@
// This is https://github.com/aws/aws-sdk-go-v2/blob/a2b751d1ba71f59175a41f9cae5f159f1044360f/aws/signer/internal/v4/header.go
// with changes:
// * drop User-Agent header from ignored
// * drop Expect header from ignored
package v4
@ -11,7 +12,7 @@ var IgnoredPresignedHeaders = Rules{
"Authorization": struct{}{},
"User-Agent": struct{}{},
"X-Amzn-Trace-Id": struct{}{},
"Expect": struct{}{},
//"Expect": struct{}{},
},
},
}
@ -24,7 +25,7 @@ var IgnoredHeaders = Rules{
"Authorization": struct{}{},
//"User-Agent": struct{}{},
"X-Amzn-Trace-Id": struct{}{},
"Expect": struct{}{},
//"Expect": struct{}{},
},
},
}

View file

@ -1,4 +1,6 @@
// This is https://github.com/aws/aws-sdk-go-v2/blob/a2b751d1ba71f59175a41f9cae5f159f1044360f/aws/signer/internal/v4/header_test.go
// with changes:
// * drop Expect header from ignored
package v4
@ -41,10 +43,10 @@ func TestIgnoredHeaders(t *testing.T) {
Header string
ExpectIgnored bool
}{
"expect": {
Header: "Expect",
ExpectIgnored: true,
},
//"expect": {
// Header: "Expect",
// ExpectIgnored: true,
//},
"authorization": {
Header: "Authorization",
ExpectIgnored: true,

View file

@ -48,7 +48,7 @@ func (o *AccessControlCache) Get(owner user.ID, key string) bool {
result, ok := entry.(bool)
if !ok {
o.logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", entry)),
zap.String("expected", fmt.Sprintf("%T", result)))
zap.String("expected", fmt.Sprintf("%T", result)), logs.TagField(logs.TagDatapath))
return false
}

View file

@ -67,7 +67,7 @@ func (o *AccessBoxCache) Get(accessKeyID string) *AccessBoxCacheValue {
result, ok := entry.(*AccessBoxCacheValue)
if !ok {
o.logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", entry)),
zap.String("expected", fmt.Sprintf("%T", result)))
zap.String("expected", fmt.Sprintf("%T", result)), logs.TagField(logs.TagDatapath))
return nil
}

View file

@ -65,7 +65,7 @@ func (o *BucketCache) GetByCID(cnrID cid.ID) *data.BucketInfo {
key, ok := entry.(string)
if !ok {
o.logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", entry)),
zap.String("expected", fmt.Sprintf("%T", key)))
zap.String("expected", fmt.Sprintf("%T", key)), logs.TagField(logs.TagDatapath))
return nil
}
@ -81,7 +81,7 @@ func (o *BucketCache) get(key string) *data.BucketInfo {
result, ok := entry.(*data.BucketInfo)
if !ok {
o.logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", entry)),
zap.String("expected", fmt.Sprintf("%T", result)))
zap.String("expected", fmt.Sprintf("%T", result)), logs.TagField(logs.TagDatapath))
return nil
}

View file

@ -69,7 +69,7 @@ func get[T any](c *FrostfsIDCache, key any) *T {
result, ok := entry.(*T)
if !ok {
c.logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", entry)),
zap.String("expected", fmt.Sprintf("%T", result)))
zap.String("expected", fmt.Sprintf("%T", result)), logs.TagField(logs.TagDatapath))
return nil
}

View file

@ -52,7 +52,7 @@ func NewListSessionCache(config *Config) *ListSessionCache {
session, ok := val.(*data.ListSession)
if !ok {
config.Logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", val)),
zap.String("expected", fmt.Sprintf("%T", session)))
zap.String("expected", fmt.Sprintf("%T", session)), logs.TagField(logs.TagDatapath))
}
if !session.Acquired.Load() {
@ -72,7 +72,7 @@ func (l *ListSessionCache) GetListSession(key ListSessionKey) *data.ListSession
result, ok := entry.(*data.ListSession)
if !ok {
l.logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", entry)),
zap.String("expected", fmt.Sprintf("%T", result)))
zap.String("expected", fmt.Sprintf("%T", result)), logs.TagField(logs.TagDatapath))
return nil
}

2
api/cache/names.go vendored
View file

@ -50,7 +50,7 @@ func (o *ObjectsNameCache) Get(key string) *oid.Address {
result, ok := entry.(oid.Address)
if !ok {
o.logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", entry)),
zap.String("expected", fmt.Sprintf("%T", result)))
zap.String("expected", fmt.Sprintf("%T", result)), logs.TagField(logs.TagDatapath))
return nil
}

View file

@ -54,7 +54,7 @@ func (c *NetworkCache) GetNetworkInfo() *netmap.NetworkInfo {
result, ok := entry.(netmap.NetworkInfo)
if !ok {
c.logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", entry)),
zap.String("expected", fmt.Sprintf("%T", result)))
zap.String("expected", fmt.Sprintf("%T", result)), logs.TagField(logs.TagDatapath))
return nil
}
@ -74,7 +74,7 @@ func (c *NetworkCache) GetNetmap() *netmap.NetMap {
result, ok := entry.(netmap.NetMap)
if !ok {
c.logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", entry)),
zap.String("expected", fmt.Sprintf("%T", result)))
zap.String("expected", fmt.Sprintf("%T", result)), logs.TagField(logs.TagDatapath))
return nil
}

View file

@ -49,7 +49,7 @@ func (o *ObjectsCache) GetObject(address oid.Address) *data.ExtendedObjectInfo {
result, ok := entry.(*data.ExtendedObjectInfo)
if !ok {
o.logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", entry)),
zap.String("expected", fmt.Sprintf("%T", result)))
zap.String("expected", fmt.Sprintf("%T", result)), logs.TagField(logs.TagDatapath))
return nil
}

View file

@ -8,14 +8,14 @@ import (
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
objecttest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/test"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest"
)
func getTestConfig() *Config {
func getTestConfig(t *testing.T) *Config {
return &Config{
Size: 10,
Lifetime: 5 * time.Second,
Logger: zap.NewExample(),
Logger: zaptest.NewLogger(t),
}
}
@ -44,7 +44,7 @@ func TestCache(t *testing.T) {
}
t.Run("check get", func(t *testing.T) {
cache := New(getTestConfig())
cache := New(getTestConfig(t))
err := cache.PutObject(extObjInfo)
require.NoError(t, err)
@ -53,7 +53,7 @@ func TestCache(t *testing.T) {
})
t.Run("check delete", func(t *testing.T) {
cache := New(getTestConfig())
cache := New(getTestConfig(t))
err := cache.PutObject(extObjInfo)
require.NoError(t, err)

View file

@ -77,7 +77,7 @@ func (l *ObjectsListCache) GetVersions(key ObjectsListKey) []*data.NodeVersion {
result, ok := entry.([]*data.NodeVersion)
if !ok {
l.logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", entry)),
zap.String("expected", fmt.Sprintf("%T", result)))
zap.String("expected", fmt.Sprintf("%T", result)), logs.TagField(logs.TagDatapath))
return nil
}
@ -96,7 +96,7 @@ func (l *ObjectsListCache) CleanCacheEntriesContainingObject(objectName string,
k, ok := key.(ObjectsListKey)
if !ok {
l.logger.Warn(logs.InvalidCacheKeyType, zap.String("actual", fmt.Sprintf("%T", key)),
zap.String("expected", fmt.Sprintf("%T", k)))
zap.String("expected", fmt.Sprintf("%T", k)), logs.TagField(logs.TagDatapath))
continue
}
if cnr.Equals(k.cid) && strings.HasPrefix(objectName, k.prefix) {

View file

@ -8,17 +8,17 @@ import (
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"
"go.uber.org/zap"
"go.uber.org/zap/zaptest"
)
const testingCacheLifetime = 5 * time.Second
const testingCacheSize = 10
func getTestObjectsListConfig() *Config {
func getTestObjectsListConfig(t *testing.T) *Config {
return &Config{
Size: testingCacheSize,
Lifetime: testingCacheLifetime,
Logger: zap.NewExample(),
Logger: zaptest.NewLogger(t),
}
}
@ -35,7 +35,7 @@ func TestObjectsListCache(t *testing.T) {
t.Run("lifetime", func(t *testing.T) {
var (
config = getTestObjectsListConfig()
config = getTestObjectsListConfig(t)
cache = NewObjectsListCache(config)
listKey = ObjectsListKey{cid: cidKey}
)
@ -53,7 +53,7 @@ func TestObjectsListCache(t *testing.T) {
t.Run("get cache with empty prefix", func(t *testing.T) {
var (
cache = NewObjectsListCache(getTestObjectsListConfig())
cache = NewObjectsListCache(getTestObjectsListConfig(t))
listKey = ObjectsListKey{cid: cidKey}
)
err := cache.PutVersions(listKey, versions)
@ -73,7 +73,7 @@ func TestObjectsListCache(t *testing.T) {
prefix: "dir",
}
cache := NewObjectsListCache(getTestObjectsListConfig())
cache := NewObjectsListCache(getTestObjectsListConfig(t))
err := cache.PutVersions(listKey, versions)
require.NoError(t, err)
@ -98,7 +98,7 @@ func TestObjectsListCache(t *testing.T) {
}
)
cache := NewObjectsListCache(getTestObjectsListConfig())
cache := NewObjectsListCache(getTestObjectsListConfig(t))
err := cache.PutVersions(listKey, versions)
require.NoError(t, err)
@ -116,7 +116,7 @@ func TestObjectsListCache(t *testing.T) {
}
)
cache := NewObjectsListCache(getTestObjectsListConfig())
cache := NewObjectsListCache(getTestObjectsListConfig(t))
err := cache.PutVersions(listKey, versions)
require.NoError(t, err)
@ -137,7 +137,7 @@ func TestCleanCacheEntriesChangedWithPutObject(t *testing.T) {
}
t.Run("put object to the root of the bucket", func(t *testing.T) {
config := getTestObjectsListConfig()
config := getTestObjectsListConfig(t)
config.Lifetime = time.Minute
cache := NewObjectsListCache(config)
for _, k := range keys {
@ -156,7 +156,7 @@ func TestCleanCacheEntriesChangedWithPutObject(t *testing.T) {
})
t.Run("put object to dir/", func(t *testing.T) {
config := getTestObjectsListConfig()
config := getTestObjectsListConfig(t)
config.Lifetime = time.Minute
cache := NewObjectsListCache(config)
for _, k := range keys {
@ -175,7 +175,7 @@ func TestCleanCacheEntriesChangedWithPutObject(t *testing.T) {
})
t.Run("put object to dir/lol/", func(t *testing.T) {
config := getTestObjectsListConfig()
config := getTestObjectsListConfig(t)
config.Lifetime = time.Minute
cache := NewObjectsListCache(config)
for _, k := range keys {

2
api/cache/policy.go vendored
View file

@ -54,7 +54,7 @@ func (o *MorphPolicyCache) Get(key MorphPolicyCacheKey) []*chain.Chain {
result, ok := entry.([]*chain.Chain)
if !ok {
o.logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", entry)),
zap.String("expected", fmt.Sprintf("%T", result)))
zap.String("expected", fmt.Sprintf("%T", result)), logs.TagField(logs.TagDatapath))
return nil
}

8
api/cache/system.go vendored
View file

@ -50,7 +50,7 @@ func (o *SystemCache) GetObject(key string) *data.ObjectInfo {
result, ok := entry.(*data.ObjectInfo)
if !ok {
o.logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", entry)),
zap.String("expected", fmt.Sprintf("%T", result)))
zap.String("expected", fmt.Sprintf("%T", result)), logs.TagField(logs.TagDatapath))
return nil
}
@ -81,7 +81,7 @@ func (o *SystemCache) GetCORS(key string) *data.CORSConfiguration {
result, ok := entry.(*data.CORSConfiguration)
if !ok {
o.logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", entry)),
zap.String("expected", fmt.Sprintf("%T", result)))
zap.String("expected", fmt.Sprintf("%T", result)), logs.TagField(logs.TagDatapath))
return nil
}
@ -97,7 +97,7 @@ func (o *SystemCache) GetLifecycleConfiguration(key string) *data.LifecycleConfi
result, ok := entry.(*data.LifecycleConfiguration)
if !ok {
o.logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", entry)),
zap.String("expected", fmt.Sprintf("%T", result)))
zap.String("expected", fmt.Sprintf("%T", result)), logs.TagField(logs.TagDatapath))
return nil
}
@ -113,7 +113,7 @@ func (o *SystemCache) GetSettings(key string) *data.BucketSettings {
result, ok := entry.(*data.BucketSettings)
if !ok {
o.logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", entry)),
zap.String("expected", fmt.Sprintf("%T", result)))
zap.String("expected", fmt.Sprintf("%T", result)), logs.TagField(logs.TagDatapath))
return nil
}

View file

@ -2,6 +2,7 @@ package data
import (
"encoding/xml"
"fmt"
"strings"
"time"
@ -20,6 +21,8 @@ const (
VersioningUnversioned = "Unversioned"
VersioningEnabled = "Enabled"
VersioningSuspended = "Suspended"
corsFilePathTemplate = "/%s.cors"
)
type (
@ -103,6 +106,11 @@ func (b *BucketInfo) CORSObjectName() string {
return b.CID.EncodeToString() + bktCORSConfigurationObject
}
// CORSObjectFilePath returns a FilePath for a bucket CORS configuration file.
func (b *BucketInfo) CORSObjectFilePath() string {
return fmt.Sprintf(corsFilePathTemplate, b.CID)
}
func (b *BucketInfo) LifecycleConfigurationObjectName() string {
return b.CID.EncodeToString() + bktLifecycleConfigurationObject
}

View file

@ -11,6 +11,7 @@ import (
"net/http"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
@ -47,7 +48,9 @@ const (
)
func (h *handler) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetBucketACL")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -99,7 +102,7 @@ func (h *handler) encodePrivateCannedACL(ctx context.Context, bktInfo *data.Buck
ownerEncodedID := ownerDisplayName
if settings.OwnerKey == nil {
h.reqLogger(ctx).Warn(logs.BucketOwnerKeyIsMissing, zap.String("owner", bktInfo.Owner.String()))
h.reqLogger(ctx).Warn(logs.BucketOwnerKeyIsMissing, zap.String("owner", bktInfo.Owner.String()), logs.TagField(logs.TagDatapath))
} else {
ownerDisplayName = settings.OwnerKey.Address()
ownerEncodedID = hex.EncodeToString(settings.OwnerKey.Bytes())
@ -127,7 +130,9 @@ func getTokenIssuerKey(box *accessbox.Box) (*keys.PublicKey, error) {
}
func (h *handler) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.PutBucketACL")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -150,7 +155,7 @@ func (h *handler) putBucketACLAPEHandler(w http.ResponseWriter, r *http.Request,
defer func() {
if errBody := r.Body.Close(); errBody != nil {
h.reqLogger(ctx).Warn(logs.CouldNotCloseRequestBody, zap.Error(errBody))
h.reqLogger(ctx).Warn(logs.CouldNotCloseRequestBody, zap.Error(errBody), logs.TagField(logs.TagDatapath))
}
}()
@ -194,7 +199,9 @@ func (h *handler) putBucketACLAPEHandler(w http.ResponseWriter, r *http.Request,
}
func (h *handler) GetObjectACLHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetObjectACL")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -216,7 +223,9 @@ func (h *handler) GetObjectACLHandler(w http.ResponseWriter, r *http.Request) {
}
func (h *handler) PutObjectACLHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.PutObjectACL")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
if _, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName); err != nil {
@ -228,7 +237,9 @@ func (h *handler) PutObjectACLHandler(w http.ResponseWriter, r *http.Request) {
}
func (h *handler) GetBucketPolicyStatusHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetBucketPolicyStatus")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -271,7 +282,9 @@ func (h *handler) GetBucketPolicyStatusHandler(w http.ResponseWriter, r *http.Re
}
func (h *handler) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetBucketPolicy")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -298,7 +311,9 @@ func (h *handler) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request)
}
func (h *handler) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.DeleteBucketPolicy")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -328,7 +343,9 @@ func checkOwner(info *data.BucketInfo, owner string) error {
}
func (h *handler) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.PutBucketPolicy")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -382,7 +399,7 @@ func (h *handler) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request)
h.logAndSendError(ctx, w, "could not convert s3 policy to native chain policy", reqInfo, err)
return
} else {
h.reqLogger(ctx).Warn(logs.PolicyCouldntBeConvertedToNativeRules)
h.reqLogger(ctx).Warn(logs.PolicyCouldntBeConvertedToNativeRules, logs.TagField(logs.TagDatapath))
}
chainsToSave := []*chain.Chain{s3Chain}

View file

@ -7,6 +7,7 @@ import (
"encoding/xml"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
@ -297,10 +298,17 @@ type createBucketInfo struct {
Key *keys.PrivateKey
}
type bucketPrm struct {
bktName string
query url.Values
box *accessbox.Box
createParams createBucketParams
}
func createBucket(hc *handlerContext, bktName string) *createBucketInfo {
box, key := createAccessBox(hc.t)
w := createBucketBase(hc, bktName, box)
w := createBucketBase(hc, bucketPrm{bktName: bktName, box: box})
assertStatus(hc.t, w, http.StatusOK)
bktInfo, err := hc.Layer().GetBucketInfo(hc.Context(), bktName)
@ -314,13 +322,32 @@ func createBucket(hc *handlerContext, bktName string) *createBucketInfo {
}
func createBucketAssertS3Error(hc *handlerContext, bktName string, box *accessbox.Box, code apierr.ErrorCode) {
w := createBucketBase(hc, bktName, box)
w := createBucketBase(hc, bucketPrm{bktName: bktName, box: box})
assertS3Error(hc.t, w, apierr.GetAPIError(code))
}
func createBucketBase(hc *handlerContext, bktName string, box *accessbox.Box) *httptest.ResponseRecorder {
w, r := prepareTestRequest(hc, bktName, "", nil)
ctx := middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box})
func createBucketWithConstraint(hc *handlerContext, bktName, constraint string) *createBucketInfo {
box, key := createAccessBox(hc.t)
var prm createBucketParams
if constraint != "" {
prm.LocationConstraint = constraint
}
w := createBucketBase(hc, bucketPrm{bktName: bktName, box: box, createParams: prm})
assertStatus(hc.t, w, http.StatusOK)
bktInfo, err := hc.Layer().GetBucketInfo(hc.Context(), bktName)
require.NoError(hc.t, err)
return &createBucketInfo{
BktInfo: bktInfo,
Box: box,
Key: key,
}
}
func createBucketBase(hc *handlerContext, prm bucketPrm) *httptest.ResponseRecorder {
w, r := prepareTestFullRequest(hc, prm.bktName, "", nil, prm.createParams)
ctx := middleware.SetBox(r.Context(), &middleware.Box{AccessBox: prm.box})
r = r.WithContext(ctx)
hc.Handler().CreateBucketHandler(w, r)
return w

View file

@ -42,6 +42,7 @@ type (
RetryMaxBackoff() time.Duration
RetryStrategy() RetryStrategy
TLSTerminationHeader() string
ListingKeepaliveThrottle() time.Duration
}
FrostFSID interface {

View file

@ -8,6 +8,7 @@ import (
"strconv"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
@ -70,7 +71,9 @@ var validAttributes = map[string]struct{}{
}
func (h *handler) GetObjectAttributesHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetObjectAttributes")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
params, err := parseGetObjectAttributeArgs(r, h.reqLogger(ctx))

View file

@ -0,0 +1,73 @@
package handler
import (
"net/http"
"strconv"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
)
const maxBucketList = 10000
// ListBucketsHandler handles bucket listing requests.
func (h *handler) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.ListBuckets")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
params, err := parseListBucketParams(r)
if err != nil {
h.logAndSendError(ctx, w, "failed to parse params", reqInfo, err)
return
}
resp, err := h.obj.ListBuckets(ctx, params)
if err != nil {
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
return
}
if err = middleware.EncodeToResponse(w, encodeListBuckets(reqInfo.User, resp, params)); err != nil {
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
}
}
func encodeListBuckets(owner string, resp layer.ListBucketsResult, params layer.ListBucketsParams) *ListBucketsResponse {
res := &ListBucketsResponse{
Owner: Owner{
ID: owner,
DisplayName: owner,
},
ContinuationToken: resp.ContinuationToken,
Prefix: params.Prefix,
}
for _, item := range resp.Containers {
res.Buckets.Buckets = append(res.Buckets.Buckets, Bucket{
Name: item.Name,
CreationDate: item.Created.UTC().Format(time.RFC3339),
BucketRegion: item.LocationConstraint,
})
}
return res
}
func parseListBucketParams(r *http.Request) (prm layer.ListBucketsParams, err error) {
prm.MaxBuckets = maxBucketList
strMaxBuckets := r.URL.Query().Get(middleware.QueryMaxBuckets)
if strMaxBuckets != "" {
if prm.MaxBuckets, err = strconv.Atoi(strMaxBuckets); err != nil || prm.MaxBuckets < 0 {
return layer.ListBucketsParams{}, errors.GetAPIError(errors.ErrInvalidMaxKeys)
}
}
prm.Prefix = r.URL.Query().Get(middleware.QueryPrefix)
prm.BucketRegion = r.URL.Query().Get(middleware.QueryBucketRegion)
prm.ContinuationToken = r.URL.Query().Get(middleware.QueryContinuationToken)
return
}

View file

@ -0,0 +1,174 @@
package handler
import (
"encoding/xml"
"net/http"
"net/http/httptest"
"net/url"
"sort"
"testing"
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
"github.com/stretchr/testify/require"
)
func TestHandler_ListBucketsHandler(t *testing.T) {
const defaultConstraint = "default"
region := "us-west-1"
hc := prepareWithoutCORSHandlerContext(t)
hc.config.putLocationConstraint(region)
props := []Bucket{
{Name: "first"},
{Name: "regional", BucketRegion: "us-west-1"},
{Name: "third"},
}
sort.Slice(props, func(i, j int) bool {
return props[i].Name < props[j].Name
})
for _, bkt := range props {
createBucketWithConstraint(hc, bkt.Name, bkt.BucketRegion)
}
for _, tt := range []struct {
title string
token string
prefix string
bucketRegion string
maxBuckets string
expectErr bool
expected []Bucket
expectedToken string
}{
{
title: "no params",
expected: []Bucket{
{Name: "first", BucketRegion: defaultConstraint},
{Name: "regional", BucketRegion: "us-west-1"},
{Name: "third", BucketRegion: defaultConstraint},
},
},
{
title: "negative max-buckets",
maxBuckets: "-1",
expected: []Bucket{},
expectErr: true,
},
{
title: "zero max-buckets",
maxBuckets: "0",
expected: []Bucket{},
},
{
title: "prefix",
prefix: "thi",
expected: []Bucket{{Name: "third", BucketRegion: defaultConstraint}},
},
{
title: "wrong prefix",
prefix: "sdh",
expected: []Bucket{},
},
{
title: "bucket region",
bucketRegion: region,
expected: []Bucket{{Name: "regional", BucketRegion: "us-west-1"}},
},
{
title: "default bucket region",
bucketRegion: defaultConstraint,
expected: []Bucket{
{Name: "first", BucketRegion: defaultConstraint},
{Name: "third", BucketRegion: defaultConstraint},
},
},
{
title: "wrong bucket region",
bucketRegion: "sj dfdlsj",
expected: []Bucket{},
},
} {
t.Run(tt.title, func(t *testing.T) {
if tt.expectErr {
listBucketsErr(hc, tt.prefix, tt.token, tt.bucketRegion, tt.maxBuckets, apierr.GetAPIError(apierr.ErrInvalidMaxKeys))
return
}
resp := listBuckets(hc, tt.prefix, tt.token, tt.bucketRegion, tt.maxBuckets)
require.Len(t, resp.Buckets.Buckets, len(tt.expected))
require.Equal(t, tt.prefix, resp.Prefix)
require.Equal(t, hc.owner.String(), resp.Owner.ID)
if len(resp.Buckets.Buckets) > 0 {
t.Log(resp.Buckets.Buckets[0].Name)
}
for i, bkt := range resp.Buckets.Buckets {
require.Equal(t, tt.expected[i].Name, bkt.Name)
require.Equal(t, tt.expected[i].BucketRegion, bkt.BucketRegion)
}
})
}
t.Run("pagination", func(t *testing.T) {
t.Run("happy path", func(t *testing.T) {
resp := listBuckets(hc, "", "", "", "1")
require.Len(t, resp.Buckets.Buckets, 1)
require.Equal(t, props[0].Name, resp.Buckets.Buckets[0].Name)
require.NotEmpty(t, resp.ContinuationToken)
resp = listBuckets(hc, "", resp.ContinuationToken, "", "1")
require.Len(t, resp.Buckets.Buckets, 1)
require.Equal(t, props[1].Name, resp.Buckets.Buckets[0].Name)
require.NotEmpty(t, resp.ContinuationToken)
resp = listBuckets(hc, "", resp.ContinuationToken, "", "1")
require.Len(t, resp.Buckets.Buckets, 1)
require.Equal(t, props[2].Name, resp.Buckets.Buckets[0].Name)
require.Empty(t, resp.ContinuationToken)
})
t.Run("wrong continuation-token", func(t *testing.T) {
resp := listBuckets(hc, "", "CebuVwfRpdMqi9dvgV2SUNbrkfteGtudchKKhNabXUu9", "", "1")
require.Len(t, resp.Buckets.Buckets, 0)
require.Empty(t, resp.ContinuationToken)
})
})
}
func listBuckets(hc *handlerContext, prefix, token, bucketRegion, maxBuckets string) ListBucketsResponse {
query := url.Values{
middleware.QueryPrefix: []string{prefix},
middleware.QueryContinuationToken: []string{token},
middleware.QueryBucketRegion: []string{bucketRegion},
middleware.QueryMaxBuckets: []string{maxBuckets},
}
w := listBucketsBase(hc, bucketPrm{query: query})
assertStatus(hc.t, w, http.StatusOK)
var resp ListBucketsResponse
err := xml.NewDecoder(w.Body).Decode(&resp)
require.NoError(hc.t, err)
return resp
}
func listBucketsErr(hc *handlerContext, prefix, token, bucketRegion, maxBuckets string, err apierr.Error) {
query := url.Values{
middleware.QueryPrefix: []string{prefix},
middleware.QueryContinuationToken: []string{token},
middleware.QueryBucketRegion: []string{bucketRegion},
middleware.QueryMaxBuckets: []string{maxBuckets},
}
w := listBucketsBase(hc, bucketPrm{query: query})
assertS3Error(hc.t, w, err)
}
func listBucketsBase(hc *handlerContext, prm bucketPrm) *httptest.ResponseRecorder {
box, _ := createAccessBox(hc.t)
w, r := prepareTestFullRequest(hc, "", "", prm.query, nil)
ctx := middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box})
r = r.WithContext(ctx)
hc.Handler().ListBucketsHandler(w, r)
return w
}

View file

@ -6,6 +6,7 @@ import (
"regexp"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
@ -40,18 +41,19 @@ func path2BucketObject(path string) (string, string, error) {
}
func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.CopyObject")
defer span.End()
var (
err error
versionID string
metadata map[string]string
tagSet map[string]string
ctx = r.Context()
reqInfo = middleware.GetReqInfo(ctx)
cannedACLStatus = aclHeadersStatus(r)
)
reqInfo := middleware.GetReqInfo(ctx)
cannedACLStatus := aclHeadersStatus(r)
src := r.Header.Get(api.AmzCopySource)
// Check https://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectVersioning.html
// Regardless of whether you have enabled versioning, each object in your bucket
@ -244,7 +246,7 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
}
}
h.reqLogger(ctx).Info(logs.ObjectIsCopied, zap.Stringer("object_id", dstObjInfo.ID))
h.reqLogger(ctx).Info(logs.ObjectIsCopied, zap.Stringer("object_id", dstObjInfo.ID), logs.TagField(logs.TagExternalStorage))
if dstEncryptionParams.Enabled() {
addSSECHeaders(w.Header(), r.Header)

View file

@ -5,10 +5,13 @@ import (
"strconv"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
qostagging "git.frostfs.info/TrueCloudLab/frostfs-qos/tagging"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/util"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
"go.uber.org/zap"
)
@ -20,7 +23,10 @@ const (
)
func (h *handler) GetBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetBucketCors")
defer span.End()
ctx = qostagging.ContextWithIOTag(ctx, util.InternalIOTag)
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -42,7 +48,10 @@ func (h *handler) GetBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
}
func (h *handler) PutBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.PutBucketCors")
defer span.End()
ctx = qostagging.ContextWithIOTag(ctx, util.InternalIOTag)
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -76,7 +85,10 @@ func (h *handler) PutBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
}
func (h *handler) DeleteBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.DeleteBucketCors")
defer span.End()
ctx = qostagging.ContextWithIOTag(ctx, util.InternalIOTag)
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -93,6 +105,9 @@ func (h *handler) DeleteBucketCorsHandler(w http.ResponseWriter, r *http.Request
}
func (h *handler) AppendCORSHeaders(w http.ResponseWriter, r *http.Request) {
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.AppendCORSHeaders")
defer span.End()
if r.Method == http.MethodOptions {
return
}
@ -101,20 +116,20 @@ func (h *handler) AppendCORSHeaders(w http.ResponseWriter, r *http.Request) {
return
}
ctx := r.Context()
ctx = qostagging.ContextWithIOTag(ctx, util.InternalIOTag)
reqInfo := middleware.GetReqInfo(ctx)
if reqInfo.BucketName == "" {
return
}
bktInfo, err := h.getBucketInfo(ctx, reqInfo.BucketName)
if err != nil {
h.reqLogger(ctx).Warn(logs.GetBucketInfo, zap.Error(err))
h.reqLogger(ctx).Warn(logs.GetBucketInfo, zap.Error(err), logs.TagField(logs.TagDatapath))
return
}
cors, err := h.obj.GetBucketCORS(ctx, bktInfo, h.cfg.NewXMLDecoder)
if err != nil {
h.reqLogger(ctx).Warn(logs.GetBucketCors, zap.Error(err))
h.reqLogger(ctx).Warn(logs.GetBucketCors, zap.Error(err), logs.TagField(logs.TagDatapath))
return
}
@ -153,7 +168,10 @@ func (h *handler) AppendCORSHeaders(w http.ResponseWriter, r *http.Request) {
}
func (h *handler) Preflight(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.Preflight")
defer span.End()
ctx = qostagging.ContextWithIOTag(ctx, util.InternalIOTag)
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketInfo(ctx, reqInfo.BucketName)
if err != nil {

View file

@ -1,12 +1,19 @@
package handler
import (
"encoding/xml"
"net/http"
"net/http/httptest"
"strings"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
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"
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
"github.com/stretchr/testify/require"
)
@ -37,26 +44,14 @@ func TestCORSOriginWildcard(t *testing.T) {
hc.Handler().CreateBucketHandler(w, r)
assertStatus(t, w, http.StatusOK)
w, r = prepareTestPayloadRequest(hc, bktName, "", strings.NewReader(body))
ctx = middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box})
r = r.WithContext(ctx)
hc.Handler().PutBucketCorsHandler(w, r)
assertStatus(t, w, http.StatusOK)
putBucketCORS(hc, bktName, body)
w, r = prepareTestPayloadRequest(hc, bktName, "", nil)
hc.Handler().GetBucketCorsHandler(w, r)
assertStatus(t, w, http.StatusOK)
getBucketCORS(hc, bktName)
hc.config.useDefaultXMLNS = true
w, r = prepareTestPayloadRequest(hc, bktName, "", strings.NewReader(bodyNoXmlns))
ctx = middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box})
r = r.WithContext(ctx)
hc.Handler().PutBucketCorsHandler(w, r)
assertStatus(t, w, http.StatusOK)
putBucketCORS(hc, bktName, bodyNoXmlns)
w, r = prepareTestPayloadRequest(hc, bktName, "", nil)
hc.Handler().GetBucketCorsHandler(w, r)
assertStatus(t, w, http.StatusOK)
getBucketCORS(hc, bktName)
}
func TestPreflight(t *testing.T) {
@ -170,11 +165,7 @@ func TestPreflightWildcardOrigin(t *testing.T) {
hc.Handler().CreateBucketHandler(w, r)
assertStatus(t, w, http.StatusOK)
w, r = prepareTestPayloadRequest(hc, bktName, "", strings.NewReader(body))
ctx = middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box})
r = r.WithContext(ctx)
hc.Handler().PutBucketCorsHandler(w, r)
assertStatus(t, w, http.StatusOK)
putBucketCORS(hc, bktName, body)
for _, tc := range []struct {
name string
@ -236,3 +227,183 @@ func TestPreflightWildcardOrigin(t *testing.T) {
})
}
}
func TestDeleteAllCORSVersions(t *testing.T) {
body := `
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedOrigin>*</AllowedOrigin>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
`
hc := prepareHandlerContext(t)
bktName := "bucket-delete-all-cors-version"
createBucket(hc, bktName)
require.Len(t, hc.tp.Objects(), 0)
for range 5 {
putBucketCORS(hc, bktName, body)
}
require.Len(t, hc.tp.Objects(), 5)
deleteBucketCORS(hc, bktName)
require.Len(t, hc.tp.Objects(), 0)
}
func TestGetLatestCORSVersion(t *testing.T) {
bodyTree := `
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedOrigin>*</AllowedOrigin>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
`
body := `
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedMethod>DELETE</AllowedMethod>
<AllowedOrigin>*</AllowedOrigin>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
`
hc := prepareHandlerContextWithMinCache(t)
bktName := "bucket-get-latest-cors"
info := createBucket(hc, bktName)
addCORSToTree(hc, bodyTree, info.BktInfo, info.BktInfo.CID)
w := getBucketCORS(hc, bktName)
requireEqualCORS(hc.t, bodyTree, w.Body.String())
hc.tp.AddCORSObject(info.BktInfo, hc.corsCnrID, body)
w = getBucketCORS(hc, bktName)
requireEqualCORS(hc.t, body, w.Body.String())
hc.tp.AddCORSObject(info.BktInfo, hc.corsCnrID, bodyTree)
w = getBucketCORS(hc, bktName)
requireEqualCORS(hc.t, bodyTree, w.Body.String())
}
func TestDeleteTreeCORSVersions(t *testing.T) {
body := `
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedOrigin>*</AllowedOrigin>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
`
hc := prepareHandlerContext(t)
bktName := "bucket-delete-tree-cors-versions"
info := createBucket(hc, bktName)
addCORSToTree(hc, body, info.BktInfo, info.BktInfo.CID)
addCORSToTree(hc, body, info.BktInfo, hc.corsCnrID)
require.Len(t, hc.tp.Objects(), 2)
putBucketCORS(hc, bktName, body)
require.Len(t, hc.tp.Objects(), 1)
addCORSToTree(hc, body, info.BktInfo, info.BktInfo.CID)
addCORSToTree(hc, body, info.BktInfo, hc.corsCnrID)
require.Len(t, hc.tp.Objects(), 3)
deleteBucketCORS(hc, bktName)
require.Len(t, hc.tp.Objects(), 0)
}
func TestDeleteCORSInDeleteBucket(t *testing.T) {
body := `
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedOrigin>*</AllowedOrigin>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
`
hc := prepareHandlerContext(t)
bktName := "bucket-delete-cors-in-delete-bucket"
info := createBucket(hc, bktName)
addCORSToTree(hc, body, info.BktInfo, hc.corsCnrID)
addCORSToTree(hc, body, info.BktInfo, info.BktInfo.CID)
hc.tp.AddCORSObject(info.BktInfo, hc.corsCnrID, body)
require.Len(t, hc.tp.Objects(), 3)
hc.owner = info.BktInfo.Owner
deleteBucket(t, hc, bktName, http.StatusNoContent)
require.Len(t, hc.tp.Objects(), 1) // CORS object in bucket container is not deleted
}
func addCORSToTree(hc *handlerContext, cors string, bkt *data.BucketInfo, corsCnrID cid.ID) {
var addr oid.Address
addr.SetContainer(corsCnrID)
addr.SetObject(oidtest.ID())
var obj object.Object
obj.SetPayload([]byte(cors))
obj.SetPayloadSize(uint64(len(cors)))
hc.tp.SetObject(addr, &obj)
meta := make(map[string]string)
meta["FileName"] = "bucket-cors"
meta["OID"] = addr.Object().EncodeToString()
meta["CID"] = addr.Container().EncodeToString()
_, err := hc.treeMock.AddNode(hc.context, bkt, "system", 0, meta)
require.NoError(hc.t, err)
}
func requireEqualCORS(t *testing.T, expected string, actual string) {
expectedCORS := &data.CORSConfiguration{}
err := xml.NewDecoder(strings.NewReader(expected)).Decode(expectedCORS)
require.NoError(t, err)
actualCORS := &data.CORSConfiguration{}
err = xml.NewDecoder(strings.NewReader(actual)).Decode(actualCORS)
require.NoError(t, err)
require.Equal(t, expectedCORS, actualCORS)
}
func putBucketCORS(hc *handlerContext, bktName string, body string) {
w, r := prepareTestPayloadRequest(hc, bktName, "", strings.NewReader(body))
box, _ := createAccessBox(hc.t)
r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box}))
hc.Handler().PutBucketCorsHandler(w, r)
assertStatus(hc.t, w, http.StatusOK)
}
func deleteBucketCORS(hc *handlerContext, bktName string) {
w, r := prepareTestPayloadRequest(hc, bktName, "", nil)
box, _ := createAccessBox(hc.t)
r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box}))
hc.Handler().DeleteBucketCorsHandler(w, r)
assertStatus(hc.t, w, http.StatusNoContent)
}
func getBucketCORS(hc *handlerContext, bktName string) *httptest.ResponseRecorder {
w, r := prepareTestPayloadRequest(hc, bktName, "", nil)
hc.Handler().GetBucketCorsHandler(w, r)
assertStatus(hc.t, w, http.StatusOK)
return w
}

View file

@ -7,6 +7,7 @@ import (
"strconv"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
@ -61,7 +62,9 @@ type DeleteObjectsResponse struct {
}
func (h *handler) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.DeleteObject")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
versionID := reqInfo.URL.Query().Get(api.QueryVersionID)
versionedObject := []*layer.VersionedObject{{
@ -128,7 +131,9 @@ func isErrObjectLocked(err error) bool {
// DeleteMultipleObjectsHandler handles multiple delete requests.
func (h *handler) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.DeleteMultipleObjects")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
// Content-Length is required and should be non-zero
@ -230,7 +235,9 @@ func (h *handler) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Re
}
func (h *handler) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.DeleteBucket")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
if err != nil {

View file

@ -8,6 +8,7 @@ import (
"strings"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
@ -148,12 +149,11 @@ func writeHeaders(h http.Header, requestHeader http.Header, extendedInfo *data.E
}
func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
var (
params *layer.RangeParams
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetObject")
defer span.End()
ctx = r.Context()
reqInfo = middleware.GetReqInfo(ctx)
)
var params *layer.RangeParams
reqInfo := middleware.GetReqInfo(ctx)
conditional := parseConditionalHeaders(r.Header, h.reqLogger(ctx))
@ -255,7 +255,7 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
}
if err = objPayload.StreamTo(w); err != nil {
h.logAndSendError(ctx, w, "could not stream object payload", reqInfo, err)
h.logError(ctx, "could not stream object payload", reqInfo, err)
return
}
}
@ -296,12 +296,12 @@ func parseConditionalHeaders(headers http.Header, log *zap.Logger) *conditionalA
if httpTime, err := parseHTTPTime(headers.Get(api.IfModifiedSince)); err == nil {
args.IfModifiedSince = httpTime
} else {
log.Warn(logs.FailedToParseHTTPTime, zap.String(api.IfModifiedSince, headers.Get(api.IfModifiedSince)), zap.Error(err))
log.Warn(logs.FailedToParseHTTPTime, zap.String(api.IfModifiedSince, headers.Get(api.IfModifiedSince)), zap.Error(err), logs.TagField(logs.TagDatapath))
}
if httpTime, err := parseHTTPTime(headers.Get(api.IfUnmodifiedSince)); err == nil {
args.IfUnmodifiedSince = httpTime
} else {
log.Warn(logs.FailedToParseHTTPTime, zap.String(api.IfUnmodifiedSince, headers.Get(api.IfUnmodifiedSince)), zap.Error(err))
log.Warn(logs.FailedToParseHTTPTime, zap.String(api.IfUnmodifiedSince, headers.Get(api.IfUnmodifiedSince)), zap.Error(err), logs.TagField(logs.TagDatapath))
}
return args

View file

@ -2,6 +2,7 @@ package handler
import (
"bytes"
"context"
"errors"
"fmt"
"io"
@ -197,6 +198,19 @@ func TestGetObject(t *testing.T) {
getObjectAssertS3Error(hc, bktName, objName, emptyVersion, apierr.ErrNoSuchKey)
}
func TestGetObjectStreamError(t *testing.T) {
hc := prepareHandlerContext(t)
bktName, objName := "bucket", "obj"
info := createBucket(hc, bktName)
putObject(hc, bktName, objName)
addr := getAddressOfLastVersion(hc, info.BktInfo, objName)
hc.tp.SetObjectStreamError(addr, 4, context.Canceled)
d, _ := getObject(hc, bktName, objName)
require.Equal(t, "cont", string(d))
}
func TestGetDeletedObject(t *testing.T) {
hc := prepareHandlerContextWithMinCache(t)
bktName, objName := "bucket", "obj"

View file

@ -22,7 +22,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
engineiam "git.frostfs.info/TrueCloudLab/policy-engine/iam"
utils "github.com/trailofbits/go-fuzz-utils"
"go.uber.org/zap/zaptest"
"go.uber.org/zap"
)
var (
@ -40,9 +40,9 @@ const (
func createTestBucketAndInitContext() {
fuzzt = new(tt.T)
log := zaptest.NewLogger(fuzzt)
log := zap.NewExample()
var err error
fuzzHc, err = prepareHandlerContextBase(layer.DefaultCachesConfigs(log))
fuzzHc, err = prepareHandlerContextBase(layer.DefaultCachesConfigs(log), log)
if err != nil {
panic(err)
}
@ -171,9 +171,9 @@ func generateHeaders(tp *utils.TypeProvider, r *http.Request, params []string) e
func InitFuzzCreateBucketHandler() {
fuzzt = new(tt.T)
log := zaptest.NewLogger(fuzzt)
log := zap.NewExample()
var err error
fuzzHc, err = prepareHandlerContextBase(layer.DefaultCachesConfigs(log))
fuzzHc, err = prepareHandlerContextBase(layer.DefaultCachesConfigs(log), log)
if err != nil {
panic(err)
}

View file

@ -23,7 +23,9 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/resolver"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/tree"
bearertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer/test"
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/object"
@ -35,6 +37,7 @@ import (
"github.com/panjf2000/ants/v2"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest"
"golang.org/x/exp/slices"
)
@ -44,12 +47,13 @@ type handlerContext struct {
}
type handlerContextBase struct {
owner user.ID
h *handler
tp *layer.TestFrostFS
tree *tree.Tree
context context.Context
config *configMock
owner user.ID
h *handler
tp *layer.TestFrostFS
tree *tree.Tree
context context.Context
config *configMock
corsCnrID cid.ID
layerFeatures *layer.FeatureSettingsMock
treeMock *tree.ServiceClientMemory
@ -74,6 +78,7 @@ func (hc *handlerContextBase) Context() context.Context {
type configMock struct {
defaultPolicy netmap.PlacementPolicy
placementPolicies map[string]netmap.PlacementPolicy
copiesNumbers map[string][]uint32
defaultCopiesNumbers []uint32
bypassContentEncodingInChunks bool
@ -86,8 +91,9 @@ func (c *configMock) DefaultPlacementPolicy(_ string) netmap.PlacementPolicy {
return c.defaultPolicy
}
func (c *configMock) PlacementPolicy(_, _ string) (netmap.PlacementPolicy, bool) {
return netmap.PlacementPolicy{}, false
func (c *configMock) PlacementPolicy(_, constraint string) (netmap.PlacementPolicy, bool) {
policy, ok := c.placementPolicies[constraint]
return policy, ok
}
func (c *configMock) CopiesNumbers(_, locationConstraint string) ([]uint32, bool) {
@ -151,8 +157,35 @@ func (c *configMock) TLSTerminationHeader() string {
return c.tlsTerminationHeader
}
func (c *configMock) ListingKeepaliveThrottle() time.Duration {
return 0
}
func (c *configMock) putLocationConstraint(constraint string) {
c.placementPolicies[constraint] = c.defaultPolicy
}
type handlerConfig struct {
cacheCfg *layer.CachesConfig
withoutCORS bool
}
func prepareHandlerContext(t *testing.T) *handlerContext {
hc, err := prepareHandlerContextBase(layer.DefaultCachesConfigs(zap.NewExample()))
log := zaptest.NewLogger(t)
hc, err := prepareHandlerContextBase(&handlerConfig{cacheCfg: layer.DefaultCachesConfigs(log)}, log)
require.NoError(t, err)
return &handlerContext{
handlerContextBase: hc,
t: t,
}
}
func prepareWithoutCORSHandlerContext(t *testing.T) *handlerContext {
log := zaptest.NewLogger(t)
hc, err := prepareHandlerContextBase(&handlerConfig{
cacheCfg: layer.DefaultCachesConfigs(log),
withoutCORS: true,
}, log)
require.NoError(t, err)
return &handlerContext{
handlerContextBase: hc,
@ -161,7 +194,8 @@ func prepareHandlerContext(t *testing.T) *handlerContext {
}
func prepareHandlerContextWithMinCache(t *testing.T) *handlerContext {
hc, err := prepareHandlerContextBase(getMinCacheConfig(zap.NewExample()))
log := zaptest.NewLogger(t)
hc, err := prepareHandlerContextBase(&handlerConfig{cacheCfg: getMinCacheConfig(log)}, log)
require.NoError(t, err)
return &handlerContext{
handlerContextBase: hc,
@ -169,13 +203,12 @@ func prepareHandlerContextWithMinCache(t *testing.T) *handlerContext {
}
}
func prepareHandlerContextBase(cacheCfg *layer.CachesConfig) (*handlerContextBase, error) {
func prepareHandlerContextBase(config *handlerConfig, log *zap.Logger) (*handlerContextBase, error) {
key, err := keys.NewPrivateKey()
if err != nil {
return nil, err
}
log := zap.NewExample()
tp := layer.NewTestFrostFS(key)
testResolver := &resolver.Resolver{Name: "test_resolver"}
@ -191,7 +224,7 @@ func prepareHandlerContextBase(cacheCfg *layer.CachesConfig) (*handlerContextBas
return nil, err
}
treeMock := tree.NewTree(memCli, zap.NewExample())
treeMock := tree.NewTree(memCli, log)
features := &layer.FeatureSettingsMock{}
@ -201,15 +234,23 @@ func prepareHandlerContextBase(cacheCfg *layer.CachesConfig) (*handlerContextBas
}
layerCfg := &layer.Config{
Cache: layer.NewCache(cacheCfg),
Cache: layer.NewCache(config.cacheCfg),
AnonKey: layer.AnonymousKey{Key: key},
Resolver: testResolver,
TreeService: treeMock,
Features: features,
GateOwner: owner,
GateKey: key,
WorkerPool: pool,
}
if !config.withoutCORS {
layerCfg.CORSCnrInfo, err = createCORSContainer(key, tp)
if err != nil {
return nil, err
}
}
var pp netmap.PlacementPolicy
err = pp.DecodeString("REP 1")
if err != nil {
@ -217,7 +258,8 @@ func prepareHandlerContextBase(cacheCfg *layer.CachesConfig) (*handlerContextBas
}
cfg := &configMock{
defaultPolicy: pp,
defaultPolicy: pp,
placementPolicies: make(map[string]netmap.PlacementPolicy),
}
h := &handler{
log: log,
@ -232,7 +274,7 @@ func prepareHandlerContextBase(cacheCfg *layer.CachesConfig) (*handlerContextBas
return nil, err
}
return &handlerContextBase{
hc := &handlerContextBase{
owner: owner,
h: h,
tp: tp,
@ -243,6 +285,44 @@ func prepareHandlerContextBase(cacheCfg *layer.CachesConfig) (*handlerContextBas
layerFeatures: features,
treeMock: memCli,
cache: layerCfg.Cache,
}
if layerCfg.CORSCnrInfo != nil {
hc.corsCnrID = layerCfg.CORSCnrInfo.CID
}
return hc, nil
}
func createCORSContainer(key *keys.PrivateKey, tp *layer.TestFrostFS) (*data.BucketInfo, error) {
bearerToken := bearertest.Token()
err := bearerToken.Sign(key.PrivateKey)
if err != nil {
return nil, err
}
bktName := "cors"
res, err := tp.CreateContainer(middleware.SetBox(context.Background(), &middleware.Box{AccessBox: &accessbox.Box{
Gate: &accessbox.GateData{
BearerToken: &bearerToken,
GateKey: key.PublicKey(),
},
}}), frostfs.PrmContainerCreate{
Name: bktName,
Policy: getPlacementPolicy(),
})
if err != nil {
return nil, err
}
var owner user.ID
user.IDFromKey(&owner, key.PrivateKey.PublicKey)
return &data.BucketInfo{
Name: bktName,
Owner: owner,
CID: res.ContainerID,
HomomorphicHashDisabled: res.HomomorphicHashDisabled,
}, nil
}

View file

@ -4,6 +4,7 @@ import (
"io"
"net/http"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
@ -27,7 +28,9 @@ func getRangeToDetectContentType(maxSize uint64) *layer.RangeParams {
}
func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.HeadObject")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -123,7 +126,9 @@ func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
}
func (h *handler) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.HeadBucket")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)

View file

@ -3,11 +3,14 @@ package handler
import (
"net/http"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
)
func (h *handler) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetBucketLocation")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)

View file

@ -10,6 +10,8 @@ import (
"net/http"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
qostagging "git.frostfs.info/TrueCloudLab/frostfs-qos/tagging"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
@ -27,7 +29,10 @@ const (
)
func (h *handler) GetBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetBucketLifecycle")
defer span.End()
ctx = qostagging.ContextWithIOTag(ctx, util.InternalIOTag)
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -49,10 +54,13 @@ func (h *handler) GetBucketLifecycleHandler(w http.ResponseWriter, r *http.Reque
}
func (h *handler) PutBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.PutBucketLifecycle")
defer span.End()
var buf bytes.Buffer
tee := io.TeeReader(r.Body, &buf)
ctx := r.Context()
ctx = qostagging.ContextWithIOTag(ctx, util.InternalIOTag)
reqInfo := middleware.GetReqInfo(ctx)
cfg := new(data.LifecycleConfiguration)
@ -115,7 +123,10 @@ func (h *handler) PutBucketLifecycleHandler(w http.ResponseWriter, r *http.Reque
}
func (h *handler) DeleteBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.DeleteBucketLifecycle")
defer span.End()
ctx = qostagging.ContextWithIOTag(ctx, util.InternalIOTag)
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)

View file

@ -1,49 +0,0 @@
package handler
import (
"net/http"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
)
const maxObjectList = 1000 // Limit number of objects in a listObjectsResponse/listObjectsVersionsResponse.
// ListBucketsHandler handles bucket listing requests.
func (h *handler) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
var (
own user.ID
res *ListBucketsResponse
ctx = r.Context()
reqInfo = middleware.GetReqInfo(ctx)
)
list, err := h.obj.ListBuckets(ctx)
if err != nil {
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
return
}
if len(list) > 0 {
own = list[0].Owner
}
res = &ListBucketsResponse{
Owner: Owner{
ID: own.String(),
DisplayName: own.String(),
},
}
for _, item := range list {
res.Buckets.Buckets = append(res.Buckets.Buckets, Bucket{
Name: item.Name,
CreationDate: item.Created.UTC().Format(time.RFC3339),
})
}
if err = middleware.EncodeToResponse(w, res); err != nil {
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
}
}

View file

@ -7,6 +7,7 @@ import (
"strconv"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
@ -26,7 +27,9 @@ const (
)
func (h *handler) PutBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.PutBucketObjectLockConfig")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -74,7 +77,9 @@ func (h *handler) PutBucketObjectLockConfigHandler(w http.ResponseWriter, r *htt
}
func (h *handler) GetBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetBucketObjectLockConfig")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -108,7 +113,9 @@ func (h *handler) GetBucketObjectLockConfigHandler(w http.ResponseWriter, r *htt
}
func (h *handler) PutObjectLegalHoldHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.PutObjectLegalHold")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -161,7 +168,9 @@ func (h *handler) PutObjectLegalHoldHandler(w http.ResponseWriter, r *http.Reque
}
func (h *handler) GetObjectLegalHoldHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetObjectLegalHold")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -199,7 +208,9 @@ func (h *handler) GetObjectLegalHoldHandler(w http.ResponseWriter, r *http.Reque
}
func (h *handler) PutObjectRetentionHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.PutObjectRetention")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -247,7 +258,9 @@ func (h *handler) PutObjectRetentionHandler(w http.ResponseWriter, r *http.Reque
}
func (h *handler) GetObjectRetentionHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetObjectRetention")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)

View file

@ -10,6 +10,7 @@ import (
"strings"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
@ -104,7 +105,9 @@ const (
)
func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.CreateMultipartUpload")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
uploadID := uuid.New()
cannedACLStatus := aclHeadersStatus(r)
@ -174,7 +177,9 @@ func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Re
}
func (h *handler) UploadPartHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.UploadPart")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -247,15 +252,16 @@ func (h *handler) UploadPartHandler(w http.ResponseWriter, r *http.Request) {
}
func (h *handler) UploadPartCopy(w http.ResponseWriter, r *http.Request) {
var (
versionID string
ctx = r.Context()
reqInfo = middleware.GetReqInfo(ctx)
queryValues = reqInfo.URL.Query()
uploadID = queryValues.Get(uploadIDHeaderName)
partNumStr = queryValues.Get(partNumberHeaderName)
additional = []zap.Field{zap.String("uploadID", uploadID), zap.String("partNumber", partNumStr)}
)
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.UploadPartCopy")
defer span.End()
var versionID string
reqInfo := middleware.GetReqInfo(ctx)
queryValues := reqInfo.URL.Query()
uploadID := queryValues.Get(uploadIDHeaderName)
partNumStr := queryValues.Get(partNumberHeaderName)
additional := []zap.Field{zap.String("uploadID", uploadID), zap.String("partNumber", partNumStr)}
partNumber, err := strconv.Atoi(partNumStr)
if err != nil || partNumber < layer.UploadMinPartNumber || partNumber > layer.UploadMaxPartNumber {
@ -381,7 +387,9 @@ func (h *handler) UploadPartCopy(w http.ResponseWriter, r *http.Request) {
}
func (h *handler) CompleteMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.CompleteMultipartUpload")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -508,7 +516,9 @@ func (h *handler) completeMultipartUpload(r *http.Request, c *layer.CompleteMult
}
func (h *handler) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.ListMultipartUploads")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -559,7 +569,9 @@ func (h *handler) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Req
}
func (h *handler) ListPartsHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.ListParts")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -623,7 +635,9 @@ func (h *handler) ListPartsHandler(w http.ResponseWriter, r *http.Request) {
}
func (h *handler) AbortMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.AbortMultipartUpload")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)

View file

@ -1,23 +1,32 @@
package handler
import (
"context"
"encoding/xml"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"go.uber.org/zap"
)
const maxObjectList = 1000 // Limit number of objects in a listObjectsResponse/listObjectsVersionsResponse.
// ListObjectsV1Handler handles objects listing requests for API version 1.
func (h *handler) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.ListObjectsV1")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
params, err := parseListObjectsArgsV1(reqInfo)
if err != nil {
@ -30,14 +39,28 @@ func (h *handler) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) {
return
}
ch := make(chan struct{})
defer close(ch)
params.Chan = ch
stopPeriodicResponseWriter := periodicXMLWriter(w, h.cfg.ListingKeepaliveThrottle(), ch)
list, err := h.obj.ListObjectsV1(ctx, params)
if err != nil {
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
logAndSendError := h.periodicWriterErrorSender(stopPeriodicResponseWriter())
logAndSendError(ctx, w, "could not list objects v1", reqInfo, err)
return
}
if err = middleware.EncodeToResponse(w, h.encodeV1(params, list)); err != nil {
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
headerIsWritten := stopPeriodicResponseWriter()
if headerIsWritten {
if err = middleware.EncodeToResponseNoHeader(w, h.encodeV1(params, list)); err != nil {
h.logAndSendErrorNoHeader(ctx, w, "could not encode listing v1 response", reqInfo, err)
}
} else {
if err = middleware.EncodeToResponse(w, h.encodeV1(params, list)); err != nil {
h.logAndSendError(ctx, w, "could not encode listing v1 response", reqInfo, err)
}
}
}
@ -62,7 +85,9 @@ func (h *handler) encodeV1(p *layer.ListObjectsParamsV1, list *layer.ListObjects
// ListObjectsV2Handler handles objects listing requests for API version 2.
func (h *handler) ListObjectsV2Handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.ListObjectsV2")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
params, err := parseListObjectsArgsV2(reqInfo)
if err != nil {
@ -75,14 +100,28 @@ func (h *handler) ListObjectsV2Handler(w http.ResponseWriter, r *http.Request) {
return
}
ch := make(chan struct{})
defer close(ch)
params.Chan = ch
stopPeriodicResponseWriter := periodicXMLWriter(w, h.cfg.ListingKeepaliveThrottle(), ch)
list, err := h.obj.ListObjectsV2(ctx, params)
if err != nil {
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
logAndSendError := h.periodicWriterErrorSender(stopPeriodicResponseWriter())
logAndSendError(ctx, w, "could not list objects v2", reqInfo, err)
return
}
if err = middleware.EncodeToResponse(w, h.encodeV2(params, list)); err != nil {
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
headerIsWritten := stopPeriodicResponseWriter()
if headerIsWritten {
if err = middleware.EncodeToResponseNoHeader(w, h.encodeV2(params, list)); err != nil {
h.logAndSendErrorNoHeader(ctx, w, "could not encode listing v2 response", reqInfo, err)
}
} else {
if err = middleware.EncodeToResponse(w, h.encodeV2(params, list)); err != nil {
h.logAndSendError(ctx, w, "could not encode listing v2 response", reqInfo, err)
}
}
}
@ -221,7 +260,9 @@ func fillContents(src []*data.ExtendedNodeVersion, encode string, fetchOwner, md
}
func (h *handler) ListBucketObjectVersionsHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.ListBucketObjectVersions")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
p, err := parseListObjectVersionsRequest(reqInfo)
if err != nil {
@ -234,15 +275,29 @@ func (h *handler) ListBucketObjectVersionsHandler(w http.ResponseWriter, r *http
return
}
info, err := h.obj.ListObjectVersions(ctx, p)
ch := make(chan struct{})
defer close(ch)
p.Chan = ch
stopPeriodicResponseWriter := periodicXMLWriter(w, h.cfg.ListingKeepaliveThrottle(), ch)
list, err := h.obj.ListObjectVersions(ctx, p)
if err != nil {
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
logAndSendError := h.periodicWriterErrorSender(stopPeriodicResponseWriter())
logAndSendError(ctx, w, "could not list objects versions", reqInfo, err)
return
}
response := encodeListObjectVersionsToResponse(p, info, p.BktInfo.Name, h.cfg.MD5Enabled())
if err = middleware.EncodeToResponse(w, response); err != nil {
h.logAndSendError(ctx, w, "something went wrong", reqInfo, err)
response := encodeListObjectVersionsToResponse(p, list, p.BktInfo.Name, h.cfg.MD5Enabled())
headerIsWritten := stopPeriodicResponseWriter()
if headerIsWritten {
if err = middleware.EncodeToResponseNoHeader(w, response); err != nil {
h.logAndSendErrorNoHeader(ctx, w, "could not encode listing versions response", reqInfo, err)
}
} else {
if err = middleware.EncodeToResponse(w, response); err != nil {
h.logAndSendError(ctx, w, "could not encode listing versions response", reqInfo, err)
}
}
}
@ -325,3 +380,70 @@ func encodeListObjectVersionsToResponse(p *layer.ListObjectVersionsParams, info
return &res
}
// periodicWriterErrorSender returns handler function to send error. If header is
// already written by periodic XML writer, do not send HTTP and XML headers.
func (h *handler) periodicWriterErrorSender(headerWritten bool) func(context.Context, http.ResponseWriter, string, *middleware.ReqInfo, error, ...zap.Field) {
if headerWritten {
return h.logAndSendErrorNoHeader
}
return h.logAndSendError
}
// periodicXMLWriter creates go routine to write xml header and whitespaces
// over time to avoid connection drop from the client. To work properly,
// pass `http.ResponseWriter` with implemented `http.Flusher` interface.
// Returns stop function which returns boolean if writer has been used
// during goroutine execution. Zero or negative duration disable writing.
func periodicXMLWriter(w io.Writer, dur time.Duration, ch <-chan struct{}) (stop func() bool) {
if dur <= 0 {
return func() bool { return false }
}
whitespaceChar := []byte(" ")
closer := make(chan struct{})
done := make(chan struct{})
headerWritten := false
go func() {
defer close(done)
var lastEvent time.Time
for {
select {
case _, ok := <-ch:
if !ok {
return
}
if time.Since(lastEvent) < dur {
continue
}
lastEvent = time.Now()
if !headerWritten {
_, err := w.Write([]byte(xml.Header))
headerWritten = err == nil
}
_, err := w.Write(whitespaceChar)
if err != nil {
return // is there anything we can do better than ignore error?
}
if buffered, ok := w.(http.Flusher); ok {
buffered.Flush()
}
case <-closer:
return
}
}
}()
stop = func() bool {
close(closer)
<-done // wait for goroutine to stop
return headerWritten
}
return stop
}

View file

@ -1,7 +1,9 @@
package handler
import (
"bytes"
"context"
"encoding/xml"
"fmt"
"net/http"
"net/http/httptest"
@ -103,7 +105,7 @@ func TestListObjectsVersionsSkipLogTaggingNodesError(t *testing.T) {
loggerCore, observedLog := observer.New(zap.DebugLevel)
log := zap.New(loggerCore)
hcBase, err := prepareHandlerContextBase(layer.DefaultCachesConfigs(log))
hcBase, err := prepareHandlerContextBase(&handlerConfig{cacheCfg: layer.DefaultCachesConfigs(log)}, log)
require.NoError(t, err)
hc := &handlerContext{
handlerContextBase: hcBase,
@ -176,7 +178,7 @@ func TestListObjectsContextCanceled(t *testing.T) {
layerCfg.SessionList.Lifetime = time.Hour
layerCfg.SessionList.Size = 1
hcBase, err := prepareHandlerContextBase(layerCfg)
hcBase, err := prepareHandlerContextBase(&handlerConfig{cacheCfg: layerCfg}, log)
require.NoError(t, err)
hc := &handlerContext{
handlerContextBase: hcBase,
@ -841,6 +843,101 @@ func TestListingsWithInvalidEncodingType(t *testing.T) {
listObjectsV1Err(hc, bktName, "invalid", apierr.GetAPIError(apierr.ErrInvalidEncodingMethod))
}
func TestPeriodicWriter(t *testing.T) {
const dur = 100 * time.Millisecond
const whitespaces = 8
expected := []byte(xml.Header)
for i := 0; i < whitespaces; i++ {
expected = append(expected, []byte(" ")...)
}
t.Run("writes data", func(t *testing.T) {
ch := make(chan struct{})
go func() {
defer close(ch)
for range whitespaces {
ch <- struct{}{}
time.Sleep(dur)
}
}()
buf := bytes.NewBuffer(nil)
stop := periodicXMLWriter(buf, time.Nanosecond, ch)
// N number of whitespaces + half durations to guarantee at least N writes in buffer
time.Sleep(whitespaces*dur + dur/2)
require.True(t, stop())
require.Equal(t, string(expected), buf.String())
t.Run("no additional data after stop", func(t *testing.T) {
time.Sleep(2 * dur)
require.Equal(t, string(expected), buf.String())
})
})
t.Run("does not write data", func(t *testing.T) {
buf := bytes.NewBuffer(nil)
ch := make(chan struct{})
go func() {
defer close(ch)
for range whitespaces {
time.Sleep(2 * dur)
ch <- struct{}{}
}
}()
stop := periodicXMLWriter(buf, time.Nanosecond, ch)
require.False(t, stop())
require.Empty(t, buf.Bytes())
})
t.Run("throttling works", func(t *testing.T) {
buf := bytes.NewBuffer(nil)
ch := make(chan struct{})
go func() {
defer close(ch)
for range whitespaces {
ch <- struct{}{}
time.Sleep(dur / 2)
}
}()
stop := periodicXMLWriter(buf, dur, ch)
// N number of whitespaces + half durations to guarantee at least N writes in buffer
time.Sleep(whitespaces*dur + dur/2)
require.True(t, stop())
require.Equal(t, string(expected[:len(expected)-4]), buf.String())
})
t.Run("disabled", func(t *testing.T) {
buf := bytes.NewBuffer(nil)
ch := make(chan struct{})
go func() {
defer close(ch)
for range whitespaces {
select {
case ch <- struct{}{}:
default:
}
time.Sleep(dur / 2)
}
}()
stop := periodicXMLWriter(buf, 0, ch)
// N number of whitespaces + half durations to guarantee at least N writes in buffer
time.Sleep(whitespaces*dur + dur/2)
require.False(t, stop())
require.Empty(t, buf.String())
})
}
func checkVersionsNames(t *testing.T, versions *ListObjectsVersionsResponse, names []string) {
for i, v := range versions.Version {
require.Equal(t, names[i], v.Key)

View file

@ -7,6 +7,7 @@ import (
"strings"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
@ -19,7 +20,9 @@ import (
const maxPatchSize = 5 * 1024 * 1024 * 1024 // 5GB
func (h *handler) PatchObjectHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.PatchObject")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
if _, ok := r.Header[api.ContentRange]; !ok {
@ -142,7 +145,7 @@ func parsePatchConditionalHeaders(headers http.Header, log *zap.Logger) *conditi
if httpTime, err := parseHTTPTime(headers.Get(api.IfUnmodifiedSince)); err == nil {
args.IfUnmodifiedSince = httpTime
} else {
log.Warn(logs.FailedToParseHTTPTime, zap.String(api.IfUnmodifiedSince, headers.Get(api.IfUnmodifiedSince)), zap.Error(err))
log.Warn(logs.FailedToParseHTTPTime, zap.String(api.IfUnmodifiedSince, headers.Get(api.IfUnmodifiedSince)), zap.Error(err), logs.TagField(logs.TagDatapath))
}
return args

View file

@ -18,6 +18,7 @@ import (
"strings"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
@ -54,17 +55,29 @@ func (p *postPolicy) condition(key string) *policyCondition {
return nil
}
func (p *postPolicy) CheckContentLength(size uint64) bool {
func (p *postPolicy) CheckContentLength(size uint64) error {
if p.empty {
return true
return nil
}
for _, condition := range p.Conditions {
if condition.Matching == "content-length-range" {
length := strconv.FormatUint(size, 10)
return condition.Key <= length && length <= condition.Value
start, err := strconv.ParseUint(condition.Key, 10, 64)
if err != nil {
return errInvalidCondition
}
end, err := strconv.ParseUint(condition.Value, 10, 64)
if err != nil {
return errInvalidCondition
}
if start <= size && size <= end {
return nil
}
return fmt.Errorf("length of the content did not fall within the range specified in the condition")
}
}
return true
return nil
}
func (p *policyCondition) match(value string) bool {
@ -184,12 +197,13 @@ type createBucketParams struct {
}
func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
var (
err error
cannedACLStatus = aclHeadersStatus(r)
ctx = r.Context()
reqInfo = middleware.GetReqInfo(ctx)
)
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.PutObject")
defer span.End()
var err error
cannedACLStatus := aclHeadersStatus(r)
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
if err != nil {
@ -466,7 +480,7 @@ func (h *handler) isTLSCheckRequired(r *http.Request) bool {
tlsTermination, err := strconv.ParseBool(tlsTerminationStr)
if err != nil {
h.reqLogger(r.Context()).Warn(logs.WarnInvalidTypeTLSTerminationHeader, zap.String("header", tlsTerminationStr), zap.Error(err))
h.reqLogger(r.Context()).Warn(logs.WarnInvalidTypeTLSTerminationHeader, zap.String("header", tlsTerminationStr), zap.Error(err), logs.TagField(logs.TagDatapath))
return true
}
@ -474,16 +488,18 @@ func (h *handler) isTLSCheckRequired(r *http.Request) bool {
}
func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
var (
tagSet map[string]string
ctx = r.Context()
reqInfo = middleware.GetReqInfo(ctx)
metadata = make(map[string]string)
)
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.PostObject")
defer span.End()
var tagSet map[string]string
reqInfo := middleware.GetReqInfo(ctx)
metadata := make(map[string]string)
policy, err := checkPostPolicy(r, reqInfo, metadata)
if err != nil {
h.logAndSendError(ctx, w, "failed check policy", reqInfo, err)
h.logAndSendError(ctx, w, "failed check policy", reqInfo,
fmt.Errorf("%w: %v", apierr.GetAPIError(apierr.ErrInvalidArgument), err))
return
}
@ -533,7 +549,8 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
if reqInfo.ObjectName == "" || strings.Contains(reqInfo.ObjectName, "${filename}") {
_, head, err := r.FormFile("file")
if err != nil {
h.logAndSendError(ctx, w, "could not parse file field", reqInfo, err)
h.logAndSendError(ctx, w, "could not parse file field", reqInfo,
fmt.Errorf("%w: %v", apierr.GetAPIError(apierr.ErrInvalidArgument), err))
return
}
filename = head.Filename
@ -542,7 +559,8 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
var head *multipart.FileHeader
contentReader, head, err = r.FormFile("file")
if err != nil {
h.logAndSendError(ctx, w, "could not parse file field", reqInfo, err)
h.logAndSendError(ctx, w, "could not parse file field", reqInfo,
fmt.Errorf("%w: %v", apierr.GetAPIError(apierr.ErrInvalidArgument), err))
return
}
size = uint64(head.Size)
@ -560,8 +578,8 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
return
}
if !policy.CheckContentLength(size) {
h.logAndSendError(ctx, w, "invalid content-length", reqInfo, apierr.GetAPIError(apierr.ErrInvalidArgument))
if err := policy.CheckContentLength(size); err != nil {
h.logAndSendError(ctx, w, err.Error(), reqInfo, apierr.GetAPIError(apierr.ErrPostPolicyConditionInvalidFormat))
return
}
@ -587,6 +605,7 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
ObjectName: objInfo.Name,
VersionID: objInfo.VersionID(),
},
TagSet: tagSet,
NodeVersion: extendedObjInfo.NodeVersion,
}
@ -764,7 +783,10 @@ func parseCannedACL(header http.Header) (string, error) {
}
func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) {
h.createBucketHandlerPolicy(w, r)
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.CreateBucket")
defer span.End()
h.createBucketHandlerPolicy(w, r.WithContext(ctx))
}
func (h *handler) parseCommonCreateBucketParams(reqInfo *middleware.ReqInfo, boxData *accessbox.Box, r *http.Request) (*keys.PublicKey, *layer.CreateBucketParams, error) {
@ -828,7 +850,7 @@ func (h *handler) createBucketHandlerPolicy(w http.ResponseWriter, r *http.Reque
h.logAndSendError(ctx, w, "could not create bucket", reqInfo, err)
return
}
h.reqLogger(ctx).Info(logs.BucketIsCreated, zap.Stringer("container_id", bktInfo.CID))
h.reqLogger(ctx).Info(logs.BucketIsCreated, zap.Stringer("container_id", bktInfo.CID), logs.TagField(logs.TagExternalStorage))
chains := bucketCannedACLToAPERules(cannedACL, reqInfo, bktInfo.CID)
if err = h.ape.SaveACLChains(bktInfo.CID.EncodeToString(), chains); err != nil {
@ -1041,7 +1063,7 @@ func isLockEnabled(log *zap.Logger, header http.Header) bool {
lockEnabled, err := strconv.ParseBool(lockEnabledStr)
if err != nil {
log.Warn(logs.InvalidBucketObjectLockEnabledHeader, zap.String("header", lockEnabledStr), zap.Error(err))
log.Warn(logs.InvalidBucketObjectLockEnabledHeader, zap.String("header", lockEnabledStr), zap.Error(err), logs.TagField(logs.TagDatapath))
}
return lockEnabled

View file

@ -9,7 +9,10 @@ import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"hash/crc32"
"io"
"mime/multipart"
"net/http"
@ -146,8 +149,31 @@ func TestPostObject(t *testing.T) {
filename string
content string
objName string
tagging string
err bool
}{
{
key: "user/user1/${filename}",
filename: "object",
content: "content",
objName: "user/user1/object",
tagging: "<Tagging xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><TagSet><Tag><Key>Environment</Key><Value>Production</Value></Tag></TagSet></Tagging>",
},
{
key: "user/user1/${filename}",
filename: "object",
content: "content",
objName: "user/user1/object",
tagging: "wrong tagging",
err: true,
},
{
key: "user/user1/${filename}",
filename: "object",
content: "content",
objName: "user/user1/object",
tagging: "<Tagging xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><TagSet></TagSet></Tagging>",
},
{
key: "user/user1/${filename}",
filename: "object",
@ -205,14 +231,21 @@ func TestPostObject(t *testing.T) {
},
} {
t.Run(tc.key+";"+tc.filename, func(t *testing.T) {
w := postObjectBase(hc, ns, bktName, tc.key, tc.filename, tc.content)
w := postObjectBase(hc, ns, bktName, tc.key, tc.filename, tc.content, tc.tagging)
if tc.err {
assertS3Error(hc.t, w, apierr.GetAPIError(apierr.ErrInternalError))
assertStatus(hc.t, w, http.StatusBadRequest)
return
}
assertStatus(hc.t, w, http.StatusNoContent)
content, _ := getObject(hc, bktName, tc.objName)
require.Equal(t, tc.content, string(content))
if tc.tagging != "" {
tagging := getObjectTagging(t, hc, bktName, tc.objName, "")
strtags, err := xml.Marshal(tagging)
require.NoError(t, err)
require.Equal(t, tc.tagging, string(strtags))
}
})
}
}
@ -426,7 +459,7 @@ func TestPutObjectWithStreamBodyAWSExampleTrailing(t *testing.T) {
createTestBucket(hc, bktName)
t.Run("valid trailer signature", func(t *testing.T) {
w, req, chunk := getChunkedRequestTrailing(hc.context, t, bktName, objName)
w, req, chunk := getChunkedRequestAWSExampleTrailing(t, bktName, objName)
hc.Handler().PutObjectHandler(w, req)
assertStatus(t, w, http.StatusOK)
@ -440,7 +473,7 @@ func TestPutObjectWithStreamBodyAWSExampleTrailing(t *testing.T) {
})
t.Run("invalid trailer signature", func(t *testing.T) {
w, req, _ := getChunkedRequestTrailing(hc.context, t, bktName, objName)
w, req, _ := getChunkedRequestAWSExampleTrailing(t, bktName, objName)
body := req.Body.(*customNopCloser)
body.Bytes()[body.Len()-2] = 'a'
hc.Handler().PutObjectHandler(w, req)
@ -454,7 +487,7 @@ func TestPutObjectWithStreamBodyAWSExample(t *testing.T) {
bktName, objName := "examplebucket", "chunkObject.txt"
createTestBucket(hc, bktName)
w, req, chunk := getChunkedRequest(hc.context, t, bktName, objName)
w, req, chunk := getChunkedRequestAWSExample(t, bktName, objName)
hc.Handler().PutObjectHandler(w, req)
assertStatus(t, w, http.StatusOK)
@ -469,13 +502,17 @@ func TestPutObjectWithStreamBodyAWSExample(t *testing.T) {
}
}
func TestPutObjectWithStreamEmptyBodyAWSExample(t *testing.T) {
func TestPutObjectWithStreamEmptyBodyAWSExampleWithContentType(t *testing.T) {
hc := prepareHandlerContext(t)
bktName, objName := "dkirillov", "tmp"
createTestBucket(hc, bktName)
w, req := getEmptyChunkedRequest(hc.context, t, bktName, objName)
signTime, err := time.Parse("20060102T150405Z", "20241003T100055Z")
require.NoError(t, err)
extra := [2]string{api.ContentType, "text/plain; charset=UTF-8"}
w, req := getChunkedRequestBase(t, bktName, objName, nil, api.StreamingContentSHA256, signTime, extra)
hc.Handler().PutObjectHandler(w, req)
assertStatus(t, w, http.StatusOK)
@ -495,11 +532,14 @@ func TestPutObjectWithStreamEmptyBody(t *testing.T) {
bktName := "bucket"
createTestBucket(hc, bktName)
signTime, err := time.Parse("20060102T150405Z", "20241003T100055Z")
require.NoError(t, err)
t.Run("unsigned", func(t *testing.T) {
t.Run("trailer", func(t *testing.T) {
objName := "unsigned trailer"
w, req := getEmptyChunkedRequestUnsigned(hc.context, t, bktName, objName)
w, req := getEmptyChunkedRequestUnsigned(t, bktName, objName)
req.Header.Del(api.ContentType)
hc.Handler().PutObjectHandler(w, req)
assertStatus(t, w, http.StatusOK)
@ -514,7 +554,7 @@ func TestPutObjectWithStreamEmptyBody(t *testing.T) {
t.Run("trailer", func(t *testing.T) {
objName := "sigv4 trailer"
w, req := getEmptyChunkedRequestWithTrailers(hc.context, t, bktName, objName)
w, req := getChunkedRequestBase(t, bktName, objName, nil, api.StreamingContentSHA256Trailer, signTime)
req.Header.Del(api.ContentType)
hc.Handler().PutObjectHandler(w, req)
assertStatus(t, w, http.StatusOK)
@ -527,7 +567,7 @@ func TestPutObjectWithStreamEmptyBody(t *testing.T) {
t.Run("no trailer", func(t *testing.T) {
objName := "sigv4 no trailer"
w, req := getEmptyChunkedRequest(hc.context, t, bktName, objName)
w, req := getChunkedRequestBase(t, bktName, objName, nil, api.StreamingContentSHA256, signTime)
req.Header.Del(api.ContentType)
hc.Handler().PutObjectHandler(w, req)
assertStatus(t, w, http.StatusOK)
@ -542,7 +582,7 @@ func TestPutObjectWithStreamEmptyBody(t *testing.T) {
t.Run("trailer", func(t *testing.T) {
objName := "sigv4a trailer"
w, req := getEmptyChunkedRequestSigv4aWithTrailers(hc.context, t, bktName, objName)
w, req := getEmptyChunkedRequestSigv4aWithTrailers(t, bktName, objName)
req.Header.Del(api.ContentType)
hc.Handler().PutObjectHandler(w, req)
assertStatus(t, w, http.StatusOK)
@ -555,7 +595,7 @@ func TestPutObjectWithStreamEmptyBody(t *testing.T) {
t.Run("no trailer", func(t *testing.T) {
objName := "sigv4a no trailer"
w, req := getEmptyChunkedRequestSigv4a(hc.context, t, bktName, objName)
w, req := getEmptyChunkedRequestSigv4a(t, bktName, objName)
req.Header.Del(api.ContentType)
hc.Handler().PutObjectHandler(w, req)
assertStatus(t, w, http.StatusOK)
@ -573,7 +613,7 @@ func TestPutChunkedTestContentEncoding(t *testing.T) {
bktName, objName := "examplebucket", "chunkObject.txt"
createTestBucket(hc, bktName)
w, req, _ := getChunkedRequest(hc.context, t, bktName, objName)
w, req, _ := getChunkedRequestAWSExample(t, bktName, objName)
req.Header.Set(api.ContentEncoding, api.AwsChunked+",gzip")
hc.Handler().PutObjectHandler(w, req)
@ -582,13 +622,13 @@ func TestPutChunkedTestContentEncoding(t *testing.T) {
resp := headObjectBase(hc, bktName, objName, emptyVersion)
require.Equal(t, "gzip", resp.Header().Get(api.ContentEncoding))
w, req, _ = getChunkedRequest(hc.context, t, bktName, objName)
w, req, _ = getChunkedRequestAWSExample(t, bktName, objName)
req.Header.Set(api.ContentEncoding, "gzip")
hc.Handler().PutObjectHandler(w, req)
assertS3Error(t, w, apierr.GetAPIError(apierr.ErrInvalidEncodingMethod))
hc.config.bypassContentEncodingInChunks = true
w, req, _ = getChunkedRequest(hc.context, t, bktName, objName)
w, req, _ = getChunkedRequestAWSExample(t, bktName, objName)
req.Header.Set(api.ContentEncoding, "gzip")
hc.Handler().PutObjectHandler(w, req)
assertStatus(t, w, http.StatusOK)
@ -597,9 +637,9 @@ func TestPutChunkedTestContentEncoding(t *testing.T) {
require.Equal(t, "gzip", resp.Header().Get(api.ContentEncoding))
}
// getChunkedRequest implements request example from
// getChunkedRequestAWSExample implements request example from
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html
func getChunkedRequest(ctx context.Context, t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request, []byte) {
func getChunkedRequestAWSExample(t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request, []byte) {
chunk := make([]byte, 65*1024)
for i := range chunk {
chunk[i] = 'a'
@ -607,12 +647,8 @@ func getChunkedRequest(ctx context.Context, t *testing.T, bktName, objName strin
chunk1 := chunk[:64*1024]
chunk2 := chunk[64*1024:]
AWSAccessKeyID := "AKIAIOSFODNN7EXAMPLE"
AWSSecretAccessKey := "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
awsCreds := aws.Credentials{AccessKeyID: AWSAccessKeyID, SecretAccessKey: AWSSecretAccessKey}
signer := v4.NewSigner()
reqBody := bytes.NewBufferString("10000;chunk-signature=ad80c730a21e5b8d04586a2213dd63b9a0e99e0e2307b0ade35a65485a288648\r\n")
_, err := reqBody.Write(chunk1)
require.NoError(t, err)
@ -630,32 +666,14 @@ func getChunkedRequest(ctx context.Context, t *testing.T, bktName, objName strin
req.Header.Set("x-amz-content-sha256", api.StreamingContentSHA256)
req.Header.Set("x-amz-decoded-content-length", strconv.Itoa(awsChunkedRequestExampleDecodedContentLength))
req.Header.Set("x-amz-storage-class", "REDUCED_REDUNDANCY")
req.Header.Set("Authorization", "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request,SignedHeaders=content-encoding;content-length;host;x-amz-content-sha256;x-amz-date;x-amz-decoded-content-length;x-amz-storage-class,Signature=4f232c4386841ef735655705268965c44a0e4690baa4adea153f7db9fa80a0a9")
signTime, err := time.Parse("20060102T150405Z", "20130524T000000Z")
require.NoError(t, err)
err = signer.SignHTTP(ctx, awsCreds, req, auth.UnsignedPayload, "s3", "us-east-1", signTime)
require.NoError(t, err)
req.Body = io.NopCloser(reqBody)
w := httptest.NewRecorder()
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName}, "")
req = req.WithContext(middleware.SetReqInfo(ctx, reqInfo))
req = req.WithContext(middleware.SetBox(req.Context(), &middleware.Box{
ClientTime: signTime,
AuthHeaders: &middleware.AuthHeader{
AccessKeyID: AWSAccessKeyID,
SignatureV4: "4f232c4386841ef735655705268965c44a0e4690baa4adea153f7db9fa80a0a9",
Region: "us-east-1",
},
AccessBox: &accessbox.Box{
Gate: &accessbox.GateData{
SecretKey: AWSSecretAccessKey,
},
},
}))
w, req := prepareReqMiddlewares(req, signTime, AWSSecretAccessKey)
return w, req, chunk
}
@ -667,9 +685,9 @@ func (c *customNopCloser) Close() error {
return nil
}
// getChunkedRequestTrailing implements request example from
// getChunkedRequestAWSExampleTrailing implements request example from
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming-trailers.html
func getChunkedRequestTrailing(ctx context.Context, t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request, []byte) {
func getChunkedRequestAWSExampleTrailing(t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request, []byte) {
chunk := make([]byte, 65*1024)
for i := range chunk {
chunk[i] = 'a'
@ -677,12 +695,8 @@ func getChunkedRequestTrailing(ctx context.Context, t *testing.T, bktName, objNa
chunk1 := chunk[:64*1024]
chunk2 := chunk[64*1024:]
AWSAccessKeyID := "AKIAIOSFODNN7EXAMPLE"
AWSSecretAccessKey := "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
awsCreds := aws.Credentials{AccessKeyID: AWSAccessKeyID, SecretAccessKey: AWSSecretAccessKey}
signer := v4.NewSigner()
reqBody := bytes.NewBufferString("10000;chunk-signature=b474d8862b1487a5145d686f57f013e54db672cee1c953b3010fb58501ef5aa2\r\n")
_, err := reqBody.Write(chunk1)
require.NoError(t, err)
@ -712,32 +726,14 @@ func getChunkedRequestTrailing(ctx context.Context, t *testing.T, bktName, objNa
req.Header.Set("x-amz-decoded-content-length", strconv.Itoa(awsChunkedRequestExampleDecodedContentLength))
req.Header.Set("x-amz-storage-class", "REDUCED_REDUNDANCY")
req.Header.Set("x-amz-trailer", "x-amz-checksum-crc32c")
req.Header.Set("Authorization", "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request,SignedHeaders=content-encoding;content-length;host;x-amz-content-sha256;x-amz-date;x-amz-decoded-content-length;x-amz-storage-class,Signature=106e2a8a18243abcf37539882f36619c00e2dfc72633413f02d3b74544bfeb8e")
signTime, err := time.Parse("20060102T150405Z", "20130524T000000Z")
require.NoError(t, err)
err = signer.SignHTTP(ctx, awsCreds, req, api.StreamingContentSHA256Trailer, "s3", "us-east-1", signTime)
require.NoError(t, err)
req.Body = &customNopCloser{Buffer: reqBody}
w := httptest.NewRecorder()
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName}, "")
req = req.WithContext(middleware.SetReqInfo(ctx, reqInfo))
req = req.WithContext(middleware.SetBox(req.Context(), &middleware.Box{
ClientTime: signTime,
AuthHeaders: &middleware.AuthHeader{
AccessKeyID: AWSAccessKeyID,
SignatureV4: "106e2a8a18243abcf37539882f36619c00e2dfc72633413f02d3b74544bfeb8e",
Region: "us-east-1",
},
AccessBox: &accessbox.Box{
Gate: &accessbox.GateData{
SecretKey: AWSSecretAccessKey,
},
},
}))
w, req := prepareReqMiddlewares(req, signTime, AWSSecretAccessKey)
return w, req, chunk
}
@ -746,8 +742,6 @@ func getChunkedRequestUnsignedTrailing(ctx context.Context, t *testing.T, bktNam
for i := range chunk {
chunk[i] = 'a'
}
//chunk1 := chunk[:64*1024]
//chunk2 := chunk[64*1024:]
AWSAccessKeyID := "9uEm8zMrGWsEDWiPCnVuQLKTiGtCEXpYXt8eBG7agupw0JDySJZMFuej7PTcPzRqBUyPtFowNu1RtvHULU8XHjie6"
AWSSecretAccessKey := "9f546428957ed7e189b7be928906ce7d1d9cb3042dd4d2d5194e28ce8c4c3b8e"
@ -764,7 +758,6 @@ func getChunkedRequestUnsignedTrailing(ctx context.Context, t *testing.T, bktNam
require.NoError(t, err)
req, err := http.NewRequest("PUT", "https://localhost:8184/"+bktName+"/"+objName, nil)
//req, err := http.NewRequest("PUT", "https://localhost:8184/test2/body", nil)
require.NoError(t, err)
req.Header.Set("x-amz-sdk-checksum-algorithm", "CRC64NVME")
req.Header.Set("content-encoding", api.AwsChunked)
@ -781,23 +774,7 @@ func getChunkedRequestUnsignedTrailing(ctx context.Context, t *testing.T, bktNam
req.Body = io.NopCloser(reqBody)
w := httptest.NewRecorder()
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName}, "")
req = req.WithContext(middleware.SetReqInfo(ctx, reqInfo))
req = req.WithContext(middleware.SetBox(req.Context(), &middleware.Box{
ClientTime: signTime,
AuthHeaders: &middleware.AuthHeader{
AccessKeyID: AWSAccessKeyID,
SignatureV4: "a075c83779d1c3c02254fbe4c9eff0a21556d15556fc6a25db69147c4838226b",
Region: "ru",
},
AccessBox: &accessbox.Box{
Gate: &accessbox.GateData{
SecretKey: AWSSecretAccessKey,
},
},
}))
w, req := prepareReqMiddlewares(req, signTime, AWSSecretAccessKey)
return w, req, chunk
}
@ -835,110 +812,113 @@ func getChunkedRequestUnsignedTrailingSmall(ctx context.Context, t *testing.T, b
req.Body = io.NopCloser(reqBody)
w := httptest.NewRecorder()
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName}, "")
req = req.WithContext(middleware.SetReqInfo(ctx, reqInfo))
req = req.WithContext(middleware.SetBox(req.Context(), &middleware.Box{
ClientTime: signTime,
AuthHeaders: &middleware.AuthHeader{
AccessKeyID: AWSAccessKeyID,
SignatureV4: "a075c83779d1c3c02254fbe4c9eff0a21556d15556fc6a25db69147c4838226b",
Region: "ru",
},
AccessBox: &accessbox.Box{
Gate: &accessbox.GateData{
SecretKey: AWSSecretAccessKey,
},
},
}))
w, req := prepareReqMiddlewares(req, signTime, AWSSecretAccessKey)
return w, req, []byte(chunk)
}
func getEmptyChunkedRequest(ctx context.Context, t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request) {
AWSAccessKeyID := "48c1K4PLVb7SvmV3PjDKEuXaMh8yZMXZ8Wx9msrkKcYw06dZeaxeiPe8vyFm2WsoeVaNt7UWEjNsVkagDs8oX4XXh"
AWSSecretAccessKey := "09260955b4eb0279dc017ba20a1ddac909cbd226c86cbb2d868e55534c8e64b0"
func getChunkedRequestBase(t *testing.T, bktName, objName string, chunks [][]byte, shaType string, signTime time.Time, extraHeaders ...[2]string) (*httptest.ResponseRecorder, *http.Request) {
creds := aws.Credentials{
AccessKeyID: "48c1K4PLVb7SvmV3PjDKEuXaMh8yZMXZ8Wx9msrkKcYw06dZeaxeiPe8vyFm2WsoeVaNt7UWEjNsVkagDs8oX4XXh",
SecretAccessKey: "09260955b4eb0279dc017ba20a1ddac909cbd226c86cbb2d868e55534c8e64b0",
}
region := "us-east-1"
service := "s3"
reqBody := bytes.NewBufferString("0;chunk-signature=311a7142c8f3a07972c3aca65c36484b513a8fee48ab7178c7225388f2ae9894\r\n\r\n")
req, err := http.NewRequest("PUT", "http://localhost:8084/"+bktName+"/"+objName, reqBody)
require.NoError(t, err)
req.Header.Set("Amz-Sdk-Invocation-Id", "8a8cd4be-aef8-8034-f08d-a6144ade41f9")
req.Header.Set("Amz-Sdk-Request", "attempt=1; max=2")
req.Header.Set(api.Authorization, "AWS4-HMAC-SHA256 Credential=48c1K4PLVb7SvmV3PjDKEuXaMh8yZMXZ8Wx9msrkKcYw06dZeaxeiPe8vyFm2WsoeVaNt7UWEjNsVkagDs8oX4XXh/20241003/us-east-1/s3/aws4_request, SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;content-encoding;content-length;content-type;host;x-amz-content-sha256;x-amz-date;x-amz-decoded-content-length, Signature=4b530ab4af2381f214941af591266b209968264a2c94337fa1efc048c7dff352")
req.Header.Set(api.ContentEncoding, "aws-chunked")
req.Header.Set(api.ContentLength, "86")
req.Header.Set(api.ContentType, "text/plain; charset=UTF-8")
req.Header.Set(api.AmzDate, "20241003T100055Z")
req.Header.Set(api.AmzContentSha256, "STREAMING-AWS4-HMAC-SHA256-PAYLOAD")
req.Header.Set(api.AmzDecodedContentLength, "0")
signTime, err := time.Parse("20060102T150405Z", "20241003T100055Z")
req, err := http.NewRequest("PUT", "http://localhost:8084/"+bktName+"/"+objName, nil)
require.NoError(t, err)
w := httptest.NewRecorder()
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName}, "")
req = req.WithContext(middleware.SetReqInfo(ctx, reqInfo))
req = req.WithContext(middleware.SetBox(req.Context(), &middleware.Box{
payloadLength := 0
for _, chunk := range chunks {
payloadLength += len(chunk)
}
for _, kv := range extraHeaders {
req.Header.Set(kv[0], kv[1])
}
req.Header.Set(api.ContentEncoding, api.AwsChunked)
req.Header.Set(api.AmzDecodedContentLength, strconv.Itoa(payloadLength))
req.Header.Set(api.AmzDate, signTime.Format("20060102T150405Z"))
req.Header.Set(api.AmzContentSha256, shaType)
if shaType == api.StreamingContentSHA256Trailer {
req.Header.Set(api.AmzTrailer, "x-amz-checksum-crc32")
}
signer := v4.NewSigner()
err = signer.SignHTTP(req.Context(), creds, req, shaType, service, region, signTime)
require.NoError(t, err)
seedSignature := strings.Split(req.Header.Get(api.Authorization), "Signature=")[1]
seed, err := hex.DecodeString(seedSignature)
require.NoError(t, err)
var reqBody bytes.Buffer
hash := crc32.NewIEEE()
newStreamSigner := v4.NewStreamSigner(creds, service, region, seed)
for _, chunk := range chunks {
_, err = hash.Write(chunk)
require.NoError(t, err)
signature, err := newStreamSigner.GetSignature(req.Context(), nil, chunk, signTime)
require.NoError(t, err)
reqBody.WriteString(fmt.Sprintf("%x;chunk-signature=%x\r\n", len(chunk), signature))
reqBody.Write(chunk)
reqBody.WriteString("\r\n")
}
signature, err := newStreamSigner.GetSignature(req.Context(), nil, nil, signTime)
require.NoError(t, err)
reqBody.WriteString(fmt.Sprintf("0;chunk-signature=%x\r\n", signature))
if shaType == api.StreamingContentSHA256Trailer {
crc32Res := hash.Sum(nil)
checksumStr := "x-amz-checksum-crc32:" + base64.StdEncoding.EncodeToString(crc32Res)
reqBody.WriteString(fmt.Sprintf("%s\r\n", checksumStr))
trailerSignature, err := newStreamSigner.GetTrailerSignature([]byte(checksumStr+"\n"), signTime)
require.NoError(t, err)
reqBody.WriteString(fmt.Sprintf("x-amz-trailer-signature:%x\r\n", trailerSignature))
}
reqBody.WriteString("\r\n")
req.Body = io.NopCloser(&reqBody)
return prepareReqMiddlewares(req, signTime, creds.SecretAccessKey)
}
func prepareReqMiddlewares(req *http.Request, signTime time.Time, secretAccessKey string) (*httptest.ResponseRecorder, *http.Request) {
authHeader := req.Header.Get(api.Authorization)
var parsed map[string]string
var region string
if strings.HasPrefix(authHeader, auth.SignaturePreambleSigV4) {
parsed = auth.NewRegexpMatcher(auth.AuthorizationFieldRegexp).GetSubmatches(authHeader)
region = parsed["region"]
} else {
parsed = auth.NewRegexpMatcher(auth.AuthorizationFieldV4aRegexp).GetSubmatches(authHeader)
region = req.Header.Get("X-Amz-Region-Set")
}
bktObj := strings.Split(req.URL.Path, "/")
box := &middleware.Box{
ClientTime: signTime,
AuthHeaders: &middleware.AuthHeader{
AccessKeyID: AWSAccessKeyID,
SignatureV4: "4b530ab4af2381f214941af591266b209968264a2c94337fa1efc048c7dff352",
Region: "us-east-1",
AccessKeyID: parsed["access_key_id"],
SignatureV4: parsed["v4_signature"],
Region: region,
},
AccessBox: &accessbox.Box{
Gate: &accessbox.GateData{
SecretKey: AWSSecretAccessKey,
},
},
}))
AccessBox: &accessbox.Box{Gate: &accessbox.GateData{SecretKey: secretAccessKey}},
}
w := httptest.NewRecorder()
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktObj[1], Object: bktObj[2]}, "")
req = req.WithContext(middleware.SetReqInfo(req.Context(), reqInfo))
req = req.WithContext(middleware.SetBox(req.Context(), box))
return w, req
}
func getEmptyChunkedRequestWithTrailers(ctx context.Context, t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request) {
AWSAccessKeyID := "3jNrmDtHtuj1uLcixaSMA4KNUhNYhv1EpUNdFnbTXgUP071pGdSZfHSLtoC8gzjF5HoD6sC3Scq33t1WvvEvjmPnt"
AWSSecretAccessKey := "f1a0d650b650149f1a83140418e88a3c5572a0103e912e326492a91c19c4488a"
body := "0;chunk-signature=4c066652066847b79395ff24db333006b4300301c92ba2055e14169d74903a59\r\n" +
"x-amz-checksum-crc32:AAAAAA==\r\n" +
"x-amz-trailer-signature:2c1dc60c130c889217ae3a9412145202e4999baa54a864d671c1c6967069eaec\r\n\r\n"
req, err := http.NewRequest("PUT", "http://localhost:8084/"+bktName+"/"+objName, bytes.NewBufferString(body))
require.NoError(t, err)
req.Header.Set("Amz-Sdk-Invocation-Id", "dc96840e-24bc-9e6c-1c9c-c1e4f616c524")
req.Header.Set("Amz-Sdk-Request", "attempt=1; max=2")
req.Header.Set(api.Authorization, "AWS4-HMAC-SHA256 Credential=3jNrmDtHtuj1uLcixaSMA4KNUhNYhv1EpUNdFnbTXgUP071pGdSZfHSLtoC8gzjF5HoD6sC3Scq33t1WvvEvjmPnt/20250213/us-east-1/s3/aws4_request, SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;content-encoding;content-length;content-type;host;x-amz-content-sha256;x-amz-date;x-amz-decoded-content-length;x-amz-sdk-checksum-algorithm;x-amz-trailer, Signature=8de71c9fb6da8156b52c2e52ff2c6bb035fe58996c7e46c403197327760456d7")
req.Header.Set(api.ContentEncoding, "aws-chunked")
req.Header.Set(api.ContentLength, "207")
req.Header.Set(api.ContentType, "text/plain; charset=UTF-8")
req.Header.Set(api.AmzDate, "20250213T140939Z")
req.Header.Set(api.AmzContentSha256, api.StreamingContentSHA256Trailer)
req.Header.Set(api.AmzDecodedContentLength, "0")
req.Header.Set("X-Amz-Trailer", "x-amz-checksum-crc32")
req.Header.Set("X-Amz-Sdk-Checksum-Algorithm", "CRC32")
signTime, err := time.Parse("20060102T150405Z", req.Header.Get(api.AmzDate))
require.NoError(t, err)
w := httptest.NewRecorder()
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName}, "")
req = req.WithContext(middleware.SetReqInfo(ctx, reqInfo))
req = req.WithContext(middleware.SetBox(req.Context(), &middleware.Box{
ClientTime: signTime,
AuthHeaders: &middleware.AuthHeader{
AccessKeyID: AWSAccessKeyID,
SignatureV4: "8de71c9fb6da8156b52c2e52ff2c6bb035fe58996c7e46c403197327760456d7",
Region: "us-east-1",
},
AccessBox: &accessbox.Box{Gate: &accessbox.GateData{SecretKey: AWSSecretAccessKey}},
}))
return w, req
}
func getEmptyChunkedRequestUnsigned(ctx context.Context, t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request) {
AWSAccessKeyID := "3jNrmDtHtuj1uLcixaSMA4KNUhNYhv1EpUNdFnbTXgUP071pGdSZfHSLtoC8gzjF5HoD6sC3Scq33t1WvvEvjmPnt"
func getEmptyChunkedRequestUnsigned(t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request) {
AWSSecretAccessKey := "f1a0d650b650149f1a83140418e88a3c5572a0103e912e326492a91c19c4488a"
reqBody := bytes.NewBufferString("0\r\nx-amz-checksum-crc64nvme:AAAAAAAAAAA=\r\n\r\n")
@ -956,24 +936,10 @@ func getEmptyChunkedRequestUnsigned(ctx context.Context, t *testing.T, bktName,
signTime, err := time.Parse("20060102T150405Z", req.Header.Get(api.AmzDate))
require.NoError(t, err)
w := httptest.NewRecorder()
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName}, "")
req = req.WithContext(middleware.SetReqInfo(ctx, reqInfo))
req = req.WithContext(middleware.SetBox(req.Context(), &middleware.Box{
ClientTime: signTime,
AuthHeaders: &middleware.AuthHeader{
AccessKeyID: AWSAccessKeyID,
SignatureV4: "1231b012c0ac313770c5a95ccf77b95b6c9b1c3760d6aa24cb8309801d56eb4a",
Region: "ru",
},
AccessBox: &accessbox.Box{Gate: &accessbox.GateData{SecretKey: AWSSecretAccessKey}},
}))
return w, req
return prepareReqMiddlewares(req, signTime, AWSSecretAccessKey)
}
func getEmptyChunkedRequestSigv4aWithTrailers(ctx context.Context, t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request) {
AWSAccessKeyID := "3jNrmDtHtuj1uLcixaSMA4KNUhNYhv1EpUNdFnbTXgUP071pGdSZfHSLtoC8gzjF5HoD6sC3Scq33t1WvvEvjmPnt"
func getEmptyChunkedRequestSigv4aWithTrailers(t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request) {
AWSSecretAccessKey := "f1a0d650b650149f1a83140418e88a3c5572a0103e912e326492a91c19c4488a"
body := "0;chunk-signature=3046022100ab9229a80d70f4d004768992881821a441a4ad4102e18de567e68216659bf497022100ec47a7a445351683557eedf893e6ed250c97af4b0415814671770b83766d69be\r\n" +
@ -991,31 +957,17 @@ func getEmptyChunkedRequestSigv4aWithTrailers(ctx context.Context, t *testing.T,
req.Header.Set(api.AmzDecodedContentLength, "0")
req.Header.Set(api.ContentLength, "367")
req.Header.Set(api.ContentType, "text/plain: charset=UTF-8")
req.Header.Set("X-Amz-Region-Set", "use-east-1")
req.Header.Set("X-Amz-Region-Set", "us-east-1")
req.Header.Set("X-Amz-Trailer", "x-amz-checksum-crc32")
req.Header.Set("X-Amz-Sdk-Checksum-Algorithm", "CRC32")
signTime, err := time.Parse("20060102T150405Z", req.Header.Get(api.AmzDate))
require.NoError(t, err)
w := httptest.NewRecorder()
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName}, "")
req = req.WithContext(middleware.SetReqInfo(ctx, reqInfo))
req = req.WithContext(middleware.SetBox(req.Context(), &middleware.Box{
ClientTime: signTime,
AuthHeaders: &middleware.AuthHeader{
AccessKeyID: AWSAccessKeyID,
SignatureV4: "304402202e1f1efcc56c588d9a94a3d8f20368686df8bfd5e8aad01fc4eff569ff38f1800220215198e3f1ba785492fe6703c4722872909ce8a09e8c9a13da90a9230c7a24b7",
Region: "us-east-1",
},
AccessBox: &accessbox.Box{Gate: &accessbox.GateData{SecretKey: AWSSecretAccessKey}},
}))
return w, req
return prepareReqMiddlewares(req, signTime, AWSSecretAccessKey)
}
func getEmptyChunkedRequestSigv4a(ctx context.Context, t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request) {
AWSAccessKeyID := "3jNrmDtHtuj1uLcixaSMA4KNUhNYhv1EpUNdFnbTXgUP071pGdSZfHSLtoC8gzjF5HoD6sC3Scq33t1WvvEvjmPnt"
func getEmptyChunkedRequestSigv4a(t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request) {
AWSSecretAccessKey := "f1a0d650b650149f1a83140418e88a3c5572a0103e912e326492a91c19c4488a"
body := "0;chunk-signature=304502203f7c598a2e9a6673bf1ca30f5f6bebd0d76a4e9d3c16531448e96c2cda22d16a0221009e7ed578da0a9781366f1461a1484e64f15707f26d4310e59514db6ff9f7e0f1**\r\n\r\n"
@ -1036,20 +988,7 @@ func getEmptyChunkedRequestSigv4a(ctx context.Context, t *testing.T, bktName, ob
signTime, err := time.Parse("20060102T150405Z", req.Header.Get(api.AmzDate))
require.NoError(t, err)
w := httptest.NewRecorder()
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName}, "")
req = req.WithContext(middleware.SetReqInfo(ctx, reqInfo))
req = req.WithContext(middleware.SetBox(req.Context(), &middleware.Box{
ClientTime: signTime,
AuthHeaders: &middleware.AuthHeader{
AccessKeyID: AWSAccessKeyID,
SignatureV4: "3046022100dc589ea513448b996809db4b314a0b8a4a775c1165c6203c7104b2f1aae1243c0221009bf3a256e7c33415eaad20c1dbfb4e14cb00b362758bc4d2aaf94ca96a5f13f9",
Region: "us-east-1",
},
AccessBox: &accessbox.Box{Gate: &accessbox.GateData{SecretKey: AWSSecretAccessKey}},
}))
return w, req
return prepareReqMiddlewares(req, signTime, AWSSecretAccessKey)
}
func TestCreateBucket(t *testing.T) {
@ -1298,6 +1237,104 @@ func TestFormEncryptionParamsBase(t *testing.T) {
}
}
func TestCheckContentLength(t *testing.T) {
contentLength := "content-length-range"
notFallError := "length of the content did not fall within the range specified in the condition"
parseError := "invalid condition"
for _, tc := range []struct {
name string
matching string
key string
value string
size uint64
errMsg string
emptyPolicy bool
}{
{
name: "valid",
matching: contentLength,
key: "0",
value: "1000",
size: 50,
},
{
name: "valid lower limit",
matching: contentLength,
key: "5",
value: "100",
size: 5,
},
{
name: "valid upper limit",
matching: contentLength,
key: "5",
value: "100",
size: 100,
},
{
name: "invalid size value (too small)",
matching: contentLength,
key: "5",
value: "100",
size: 2,
errMsg: notFallError,
},
{
name: "invalid size value (to high)",
matching: contentLength,
key: "5",
value: "100",
size: 200,
errMsg: notFallError,
},
{
name: "no matching",
},
{
name: "invalid key type",
matching: contentLength,
key: "invalid",
value: "100",
size: 10,
errMsg: parseError,
},
{
name: "invalid value type",
matching: contentLength,
key: "5",
value: "invalid",
size: 10,
errMsg: parseError,
},
{
name: "empty policy",
emptyPolicy: true,
},
} {
t.Run(tc.name, func(t *testing.T) {
policy := &postPolicy{
Conditions: []*policyCondition{
{
Matching: tc.matching,
Key: tc.key,
Value: tc.value,
},
},
empty: tc.emptyPolicy,
}
err := policy.CheckContentLength(tc.size)
if tc.errMsg != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tc.errMsg)
return
}
require.NoError(t, err)
})
}
}
func prepareRequestForEncryption(hc *handlerContext, algo, key, md5, tlsTermination string, reqWithoutTLS, reqWithoutSSE, isCopySource bool) *http.Request {
r := httptest.NewRequest(http.MethodPost, "/", nil)
@ -1326,8 +1363,8 @@ func prepareRequestForEncryption(hc *handlerContext, algo, key, md5, tlsTerminat
return r
}
func postObjectBase(hc *handlerContext, ns, bktName, key, filename, content string) *httptest.ResponseRecorder {
policy := "eyJleHBpcmF0aW9uIjogIjIwMjUtMTItMDFUMTI6MDA6MDAuMDAwWiIsImNvbmRpdGlvbnMiOiBbCiBbInN0YXJ0cy13aXRoIiwgIiR4LWFtei1jcmVkZW50aWFsIiwgIiJdLAogWyJzdGFydHMtd2l0aCIsICIkeC1hbXotZGF0ZSIsICIiXSwKIFsic3RhcnRzLXdpdGgiLCAiJGtleSIsICIiXQpdfQ=="
func postObjectBase(hc *handlerContext, ns, bktName, key, filename, content, tagging string) *httptest.ResponseRecorder {
policy := "eyJleHBpcmF0aW9uIjogIjIwMjUtMTItMDFUMTI6MDA6MDAuMDAwWiIsImNvbmRpdGlvbnMiOiBbCiBbInN0YXJ0cy13aXRoIiwgIiR4LWFtei1jcmVkZW50aWFsIiwgIiJdLAogWyJzdGFydHMtd2l0aCIsICIkeC1hbXotZGF0ZSIsICIiXSwKIFsic3RhcnRzLXdpdGgiLCAiJGtleSIsICIiXSwKIFsic3RhcnRzLXdpdGgiLCAiJHRhZ2dpbmciLCAiIl0KXX0K"
timeToSign := time.Now()
timeToSignStr := timeToSign.Format("20060102T150405Z")
@ -1340,7 +1377,7 @@ func postObjectBase(hc *handlerContext, ns, bktName, key, filename, content stri
creds := getCredsStr(accessKeyID, timeToSignStr, region, service)
sign := auth.SignStr(secretKey, service, region, timeToSign, policy)
body, contentType, err := getMultipartFormBody(policy, creds, timeToSignStr, sign, key, filename, content)
body, contentType, err := getMultipartFormBody(policy, creds, timeToSignStr, sign, key, filename, content, tagging)
require.NoError(hc.t, err)
w, r := prepareTestPostRequest(hc, bktName, body)
@ -1358,7 +1395,7 @@ func getCredsStr(accessKeyID, timeToSign, region, service string) string {
return accessKeyID + "/" + timeToSign + "/" + region + "/" + service + "/aws4_request"
}
func getMultipartFormBody(policy, creds, date, sign, key, filename, content string) (io.Reader, string, error) {
func getMultipartFormBody(policy, creds, date, sign, key, filename, content, tagging string) (io.Reader, string, error) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
defer writer.Close()
@ -1379,6 +1416,9 @@ func getMultipartFormBody(policy, creds, date, sign, key, filename, content stri
if err := writer.WriteField(strings.ToLower(auth.AmzSignature), sign); err != nil {
return nil, "", err
}
if err := writer.WriteField("tagging", tagging); err != nil {
return nil, "", err
}
file, err := writer.CreateFormFile("file", filename)
if err != nil {

View file

@ -15,6 +15,9 @@ type ListBucketsResponse struct {
Buckets struct {
Buckets []Bucket `xml:"Bucket"`
} // Buckets are nested
ContinuationToken string `xml:"ContinuationToken,omitempty"`
Prefix string `xml:"Prefix,omitempty"`
}
// ListObjectsV1Response -- format for ListObjectsV1 response.
@ -51,8 +54,9 @@ type ListObjectsV2Response struct {
// Bucket container for bucket metadata.
type Bucket struct {
Name string
CreationDate string // time string of format "2006-01-02T15:04:05.000Z"
Name string `xml:"Name"`
CreationDate string `xml:"CreationDate"` // time string of format "2006-01-02T15:04:05.000Z"
BucketRegion string `xml:"BucketRegion,omitempty"`
}
// PolicyStatus contains status of bucket policy.

View file

@ -6,6 +6,7 @@ import (
"strings"
"unicode"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
@ -21,7 +22,9 @@ const (
)
func (h *handler) PutObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.PutObjectTagging")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
tagSet, err := h.readTagSet(reqInfo.Tagging)
@ -54,7 +57,9 @@ func (h *handler) PutObjectTaggingHandler(w http.ResponseWriter, r *http.Request
}
func (h *handler) GetObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetObjectTagging")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -92,7 +97,9 @@ func (h *handler) GetObjectTaggingHandler(w http.ResponseWriter, r *http.Request
}
func (h *handler) DeleteObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.DeleteObjectTagging")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -116,7 +123,9 @@ func (h *handler) DeleteObjectTaggingHandler(w http.ResponseWriter, r *http.Requ
}
func (h *handler) PutBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.PutBucketTagging")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
tagSet, err := h.readTagSet(reqInfo.Tagging)
@ -138,7 +147,9 @@ func (h *handler) PutBucketTaggingHandler(w http.ResponseWriter, r *http.Request
}
func (h *handler) GetBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetBucketTagging")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
@ -160,7 +171,9 @@ func (h *handler) GetBucketTaggingHandler(w http.ResponseWriter, r *http.Request
}
func (h *handler) DeleteBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.DeleteBucketTagging")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)

View file

@ -39,9 +39,40 @@ func (h *handler) logAndSendError(ctx context.Context, w http.ResponseWriter, lo
zap.String("object", reqInfo.ObjectName),
zap.String("description", logText),
zap.String("user", reqInfo.User),
zap.Error(err)}
zap.Error(err),
}
fields = append(fields, additional...)
h.reqLogger(ctx).Error(logs.RequestFailed, fields...)
h.reqLogger(ctx).Error(logs.RequestFailed, append(fields, logs.TagField(logs.TagDatapath))...)
}
func (h *handler) logAndSendErrorNoHeader(ctx context.Context, w http.ResponseWriter, logText string, reqInfo *middleware.ReqInfo, err error, additional ...zap.Field) {
err = handleDeleteMarker(w, err)
if wrErr := middleware.WriteErrorResponseNoHeader(w, reqInfo, apierr.TransformToS3Error(err)); wrErr != nil {
additional = append(additional, zap.NamedError("write_response_error", wrErr))
}
fields := []zap.Field{
zap.String("method", reqInfo.API),
zap.String("bucket", reqInfo.BucketName),
zap.String("object", reqInfo.ObjectName),
zap.String("description", logText),
zap.String("user", reqInfo.User),
zap.Error(err),
}
fields = append(fields, additional...)
h.reqLogger(ctx).Error(logs.RequestFailed, append(fields, logs.TagField(logs.TagDatapath))...)
}
func (h *handler) logError(ctx context.Context, logText string, reqInfo *middleware.ReqInfo, err error, additional ...zap.Field) {
fields := []zap.Field{
zap.String("method", reqInfo.API),
zap.String("bucket", reqInfo.BucketName),
zap.String("object", reqInfo.ObjectName),
zap.String("description", logText),
zap.String("user", reqInfo.User),
zap.Error(err),
}
fields = append(fields, additional...)
h.reqLogger(ctx).Error(logs.RequestFailed, append(fields, logs.TagField(logs.TagDatapath))...)
}
func handleDeleteMarker(w http.ResponseWriter, err error) error {

View file

@ -3,6 +3,7 @@ package handler
import (
"net/http"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
@ -10,7 +11,9 @@ import (
)
func (h *handler) PutBucketVersioningHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.PutBucketVersioning")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
configuration := new(VersioningConfiguration)
@ -57,7 +60,9 @@ func (h *handler) PutBucketVersioningHandler(w http.ResponseWriter, r *http.Requ
// GetBucketVersioningHandler implements bucket versioning getter handler.
func (h *handler) GetBucketVersioningHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetBucketVersioning")
defer span.End()
reqInfo := middleware.GetReqInfo(ctx)
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)

View file

@ -63,6 +63,7 @@ const (
AmzPartNumberMarker = "X-Amz-Part-Number-Marker"
AmzStorageClass = "X-Amz-Storage-Class"
AmzForceBucketDelete = "X-Amz-Force-Delete-Bucket"
AmzTrailer = "X-Amz-Trailer"
AmzServerSideEncryptionCustomerAlgorithm = "x-amz-server-side-encryption-customer-algorithm"
AmzServerSideEncryptionCustomerKey = "x-amz-server-side-encryption-customer-key"

View file

@ -76,7 +76,8 @@ func (c *Cache) PutBucket(bktInfo *data.BucketInfo) {
zap.String("zone", bktInfo.Zone),
zap.String("bucket name", bktInfo.Name),
zap.Stringer("bucket cid", bktInfo.CID),
zap.Error(err))
zap.Error(err),
logs.TagField(logs.TagDatapath))
}
}
@ -118,11 +119,12 @@ func (c *Cache) PutObject(owner user.ID, extObjInfo *data.ExtendedObjectInfo) {
if err := c.objCache.PutObject(extObjInfo); err != nil {
c.logger.Warn(logs.CouldntAddObjectToCache, zap.Error(err),
zap.String("object_name", extObjInfo.ObjectInfo.Name), zap.String("bucket_name", extObjInfo.ObjectInfo.Bucket),
zap.String("cid", extObjInfo.ObjectInfo.CID.EncodeToString()), zap.String("oid", extObjInfo.ObjectInfo.ID.EncodeToString()))
zap.String("cid", extObjInfo.ObjectInfo.CID.EncodeToString()), zap.String("oid", extObjInfo.ObjectInfo.ID.EncodeToString()),
logs.TagField(logs.TagDatapath))
}
if err := c.accessCache.Put(owner, extObjInfo.ObjectInfo.Address().EncodeToString()); err != nil {
c.logger.Warn(logs.CouldntCacheAccessControlOperation, zap.Error(err))
c.logger.Warn(logs.CouldntCacheAccessControlOperation, zap.Error(err), logs.TagField(logs.TagDatapath))
}
}
@ -132,7 +134,8 @@ func (c *Cache) PutObjectWithName(owner user.ID, extObjInfo *data.ExtendedObject
if err := c.namesCache.Put(extObjInfo.ObjectInfo.NiceName(), extObjInfo.ObjectInfo.Address()); err != nil {
c.logger.Warn(logs.CouldntPutObjAddressToNameCache,
zap.String("obj nice name", extObjInfo.ObjectInfo.NiceName()),
zap.Error(err))
zap.Error(err),
logs.TagField(logs.TagDatapath))
}
}
@ -146,11 +149,11 @@ func (c *Cache) GetList(owner user.ID, key cache.ObjectsListKey) []*data.NodeVer
func (c *Cache) PutList(owner user.ID, key cache.ObjectsListKey, list []*data.NodeVersion) {
if err := c.listsCache.PutVersions(key, list); err != nil {
c.logger.Warn(logs.CouldntCacheListOfObjects, zap.Error(err))
c.logger.Warn(logs.CouldntCacheListOfObjects, zap.Error(err), logs.TagField(logs.TagDatapath))
}
if err := c.accessCache.Put(owner, key.String()); err != nil {
c.logger.Warn(logs.CouldntCacheAccessControlOperation, zap.Error(err))
c.logger.Warn(logs.CouldntCacheAccessControlOperation, zap.Error(err), logs.TagField(logs.TagDatapath))
}
}
@ -164,11 +167,11 @@ func (c *Cache) GetListSession(owner user.ID, key cache.ListSessionKey) *data.Li
func (c *Cache) PutListSession(owner user.ID, key cache.ListSessionKey, session *data.ListSession) {
if err := c.sessionListCache.PutListSession(key, session); err != nil {
c.logger.Warn(logs.CouldntCacheListSession, zap.Error(err))
c.logger.Warn(logs.CouldntCacheListSession, zap.Error(err), logs.TagField(logs.TagDatapath))
}
if err := c.accessCache.Put(owner, key.String()); err != nil {
c.logger.Warn(logs.CouldntCacheAccessControlOperation, zap.Error(err))
c.logger.Warn(logs.CouldntCacheAccessControlOperation, zap.Error(err), logs.TagField(logs.TagDatapath))
}
}
@ -187,11 +190,11 @@ func (c *Cache) GetTagging(owner user.ID, key string) map[string]string {
func (c *Cache) PutTagging(owner user.ID, key string, tags map[string]string) {
if err := c.systemCache.PutTagging(key, tags); err != nil {
c.logger.Error(logs.CouldntCacheTags, zap.Error(err))
c.logger.Error(logs.CouldntCacheTags, zap.Error(err), logs.TagField(logs.TagDatapath))
}
if err := c.accessCache.Put(owner, key); err != nil {
c.logger.Warn(logs.CouldntCacheAccessControlOperation, zap.Error(err))
c.logger.Warn(logs.CouldntCacheAccessControlOperation, zap.Error(err), logs.TagField(logs.TagDatapath))
}
}
@ -209,11 +212,11 @@ func (c *Cache) GetLockInfo(owner user.ID, key string) *data.LockInfo {
func (c *Cache) PutLockInfo(owner user.ID, key string, lockInfo *data.LockInfo) {
if err := c.systemCache.PutLockInfo(key, lockInfo); err != nil {
c.logger.Error(logs.CouldntCacheLockInfo, zap.Error(err))
c.logger.Error(logs.CouldntCacheLockInfo, zap.Error(err), logs.TagField(logs.TagDatapath))
}
if err := c.accessCache.Put(owner, key); err != nil {
c.logger.Warn(logs.CouldntCacheAccessControlOperation, zap.Error(err))
c.logger.Warn(logs.CouldntCacheAccessControlOperation, zap.Error(err), logs.TagField(logs.TagDatapath))
}
}
@ -230,11 +233,11 @@ func (c *Cache) GetSettings(owner user.ID, bktInfo *data.BucketInfo) *data.Bucke
func (c *Cache) PutSettings(owner user.ID, bktInfo *data.BucketInfo, settings *data.BucketSettings) {
key := bktInfo.Name + bktInfo.SettingsObjectName()
if err := c.systemCache.PutSettings(key, settings); err != nil {
c.logger.Warn(logs.CouldntCacheBucketSettings, zap.String("bucket", bktInfo.Name), zap.Error(err))
c.logger.Warn(logs.CouldntCacheBucketSettings, zap.String("bucket", bktInfo.Name), zap.Error(err), logs.TagField(logs.TagDatapath))
}
if err := c.accessCache.Put(owner, key); err != nil {
c.logger.Warn(logs.CouldntCacheAccessControlOperation, zap.Error(err))
c.logger.Warn(logs.CouldntCacheAccessControlOperation, zap.Error(err), logs.TagField(logs.TagDatapath))
}
}
@ -252,11 +255,11 @@ func (c *Cache) PutCORS(owner user.ID, bkt *data.BucketInfo, cors *data.CORSConf
key := bkt.CORSObjectName()
if err := c.systemCache.PutCORS(key, cors); err != nil {
c.logger.Warn(logs.CouldntCacheCors, zap.String("bucket", bkt.Name), zap.Error(err))
c.logger.Warn(logs.CouldntCacheCors, zap.String("bucket", bkt.Name), zap.Error(err), logs.TagField(logs.TagDatapath))
}
if err := c.accessCache.Put(owner, key); err != nil {
c.logger.Warn(logs.CouldntCacheAccessControlOperation, zap.Error(err))
c.logger.Warn(logs.CouldntCacheAccessControlOperation, zap.Error(err), logs.TagField(logs.TagDatapath))
}
}
@ -278,11 +281,11 @@ func (c *Cache) PutLifecycleConfiguration(owner user.ID, bkt *data.BucketInfo, c
key := bkt.LifecycleConfigurationObjectName()
if err := c.systemCache.PutLifecycleConfiguration(key, cfg); err != nil {
c.logger.Warn(logs.CouldntCacheLifecycleConfiguration, zap.String("bucket", bkt.Name), zap.Error(err))
c.logger.Warn(logs.CouldntCacheLifecycleConfiguration, zap.String("bucket", bkt.Name), zap.Error(err), logs.TagField(logs.TagDatapath))
}
if err := c.accessCache.Put(owner, key); err != nil {
c.logger.Warn(logs.CouldntCacheAccessControlOperation, zap.Error(err))
c.logger.Warn(logs.CouldntCacheAccessControlOperation, zap.Error(err), logs.TagField(logs.TagDatapath))
}
}
@ -296,7 +299,7 @@ func (c *Cache) GetNetworkInfo() *netmap.NetworkInfo {
func (c *Cache) PutNetworkInfo(info netmap.NetworkInfo) {
if err := c.networkCache.PutNetworkInfo(info); err != nil {
c.logger.Warn(logs.CouldntCacheNetworkInfo, zap.Error(err))
c.logger.Warn(logs.CouldntCacheNetworkInfo, zap.Error(err), logs.TagField(logs.TagDatapath))
}
}
@ -306,7 +309,7 @@ func (c *Cache) GetNetmap() *netmap.NetMap {
func (c *Cache) PutNetmap(nm netmap.NetMap) {
if err := c.networkCache.PutNetmap(nm); err != nil {
c.logger.Warn(logs.CouldntCacheNetmap, zap.Error(err))
c.logger.Warn(logs.CouldntCacheNetmap, zap.Error(err), logs.TagField(logs.TagDatapath))
}
}

View file

@ -5,12 +5,16 @@ import (
"errors"
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/tree"
)
func (n *Layer) GetObjectTaggingAndLock(ctx context.Context, objVersion *data.ObjectVersion, nodeVersion *data.NodeVersion) (map[string]string, data.LockInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.GetObjectTaggingAndLock")
defer span.End()
var err error
owner := n.BearerOwner(ctx)

View file

@ -3,7 +3,9 @@ package layer
import (
"context"
"fmt"
"sort"
"strconv"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
@ -62,6 +64,7 @@ func (n *Layer) containerInfo(ctx context.Context, prm frostfs.PrmContainer) (*d
log.Error(logs.CouldNotParseContainerObjectLockEnabledAttribute,
zap.String("lock_enabled", attrLockEnabled),
zap.Error(err),
logs.TagField(logs.TagDatapath),
)
}
}
@ -76,7 +79,7 @@ func (n *Layer) containerInfo(ctx context.Context, prm frostfs.PrmContainer) (*d
return info, nil
}
func (n *Layer) containerList(ctx context.Context) ([]*data.BucketInfo, error) {
func (n *Layer) containerList(ctx context.Context, listParams ListBucketsParams) ([]*data.BucketInfo, error) {
stoken := n.SessionTokenForRead(ctx)
prm := frostfs.PrmUserContainers{
@ -86,7 +89,7 @@ func (n *Layer) containerList(ctx context.Context) ([]*data.BucketInfo, error) {
res, err := n.frostFS.UserContainers(ctx, prm)
if err != nil {
n.reqLogger(ctx).Error(logs.CouldNotListUserContainers, zap.Error(err))
n.reqLogger(ctx).Error(logs.CouldNotListUserContainers, zap.Error(err), logs.TagField(logs.TagExternalStorage))
return nil, err
}
@ -98,14 +101,38 @@ func (n *Layer) containerList(ctx context.Context) ([]*data.BucketInfo, error) {
}
info, err := n.containerInfo(ctx, getPrm)
if err != nil {
n.reqLogger(ctx).Error(logs.CouldNotFetchContainerInfo, zap.Error(err))
n.reqLogger(ctx).Error(logs.CouldNotFetchContainerInfo, zap.Error(err), logs.TagField(logs.TagExternalStorage))
continue
}
if shouldSkipBucket(info, listParams) {
continue
}
list = append(list, info)
}
return list, nil
sort.Slice(list, func(i, j int) bool {
return list[i].Name < list[j].Name
})
for i, info := range list {
if listParams.ContinuationToken != "" && info.Name != listParams.ContinuationToken {
continue
}
return list[i:], nil
}
return nil, nil
}
func shouldSkipBucket(info *data.BucketInfo, prm ListBucketsParams) bool {
if !strings.HasPrefix(info.Name, prm.Prefix) ||
(prm.BucketRegion != "" && info.LocationConstraint != prm.BucketRegion) {
return true
}
return false
}
func (n *Layer) createContainer(ctx context.Context, p *CreateBucketParams) (*data.BucketInfo, error) {

View file

@ -8,6 +8,7 @@ import (
"fmt"
"io"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
@ -23,6 +24,9 @@ const wildcard = "*"
var supportedMethods = map[string]struct{}{"GET": {}, "HEAD": {}, "POST": {}, "PUT": {}, "DELETE": {}}
func (n *Layer) PutBucketCORS(ctx context.Context, p *PutCORSParams) error {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.PutBucketCORS")
defer span.End()
var (
buf bytes.Buffer
tee = io.TeeReader(p.Reader, &buf)
@ -42,41 +46,36 @@ func (n *Layer) PutBucketCORS(ctx context.Context, p *PutCORSParams) error {
}
prm := frostfs.PrmObjectCreate{
Container: n.corsCnrInfo.CID,
Payload: &buf,
Filepath: p.BktInfo.CORSObjectName(),
Filepath: p.BktInfo.CORSObjectFilePath(),
CreationTime: TimeNow(ctx),
CopiesNumber: p.CopiesNumbers,
PrmAuth: frostfs.PrmAuth{
PrivateKey: &n.gateKey.PrivateKey,
},
}
var corsBkt *data.BucketInfo
if n.corsCnrInfo == nil {
corsBkt = p.BktInfo
prm.CopiesNumber = p.CopiesNumbers
} else {
corsBkt = n.corsCnrInfo
prm.PrmAuth.PrivateKey = &n.gateKey.PrivateKey
}
prm.Container = corsBkt.CID
createdObj, err := n.objectPutAndHash(ctx, prm, corsBkt)
_, err := n.objectPutAndHash(ctx, prm, n.corsCnrInfo)
if err != nil {
return fmt.Errorf("put cors object: %w", err)
}
objsToDelete, err := n.treeService.PutBucketCORS(ctx, p.BktInfo, newAddress(corsBkt.CID, createdObj.ID))
objToDeleteNotFound := errors.Is(err, tree.ErrNoNodeToRemove)
if err != nil && !objToDeleteNotFound {
return err
n.cache.PutCORS(n.BearerOwner(ctx), p.BktInfo, cors)
objs, err := n.treeService.DeleteBucketCORS(ctx, p.BktInfo)
objNotFound := errors.Is(err, tree.ErrNoNodeToRemove)
if err != nil && !objNotFound {
n.reqLogger(ctx).Error(logs.CouldntDeleteBucketCORS, zap.Error(err), logs.TagField(logs.TagExternalStorageTree))
return nil
}
if !objToDeleteNotFound {
for _, addr := range objsToDelete {
if !objNotFound {
for _, addr := range objs {
n.deleteCORSObject(ctx, p.BktInfo, addr)
}
}
n.cache.PutCORS(n.BearerOwner(ctx), p.BktInfo, cors)
return nil
}
@ -92,11 +91,15 @@ func (n *Layer) deleteCORSObject(ctx context.Context, bktInfo *data.BucketInfo,
if err := n.objectDeleteWithAuth(ctx, corsBkt, addr.Object(), prmAuth); err != nil {
n.reqLogger(ctx).Error(logs.CouldntDeleteCorsObject, zap.Error(err),
zap.String("cnrID", corsBkt.CID.EncodeToString()),
zap.String("objID", addr.Object().EncodeToString()))
zap.String("objID", addr.Object().EncodeToString()),
logs.TagField(logs.TagExternalStorage))
}
}
func (n *Layer) GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo, decoder func(io.Reader, string) *xml.Decoder) (*data.CORSConfiguration, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.GetBucketCORS")
defer span.End()
cors, err := n.getCORS(ctx, bktInfo, decoder)
if err != nil {
return nil, err
@ -106,10 +109,25 @@ func (n *Layer) GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo, dec
}
func (n *Layer) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) error {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.DeleteBucketCORS")
defer span.End()
corsVersions, err := n.getCORSVersions(ctx, bktInfo)
if err != nil {
return fmt.Errorf("get cors versions: %w", err)
}
sortedVersions := corsVersions.GetSorted()
for _, version := range sortedVersions {
if err = n.objectDeleteWithAuth(ctx, n.corsCnrInfo, version.ObjID, frostfs.PrmAuth{PrivateKey: &n.gateKey.PrivateKey}); err != nil {
return fmt.Errorf("delete cors object '%s': %w", version.VersionID(), err)
}
}
objs, err := n.treeService.DeleteBucketCORS(ctx, bktInfo)
objNotFound := errors.Is(err, tree.ErrNoNodeToRemove)
if err != nil && !objNotFound {
return err
return fmt.Errorf("delete cors from tree: %w", err)
}
if !objNotFound {
@ -123,6 +141,22 @@ func (n *Layer) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo)
return nil
}
func (n *Layer) deleteCORSVersions(ctx context.Context, bktInfo *data.BucketInfo) {
corsVersions, err := n.getCORSVersions(ctx, bktInfo)
if err != nil {
n.reqLogger(ctx).Error(logs.CouldntGetCORSObjectVersions, zap.Error(err), logs.TagField(logs.TagExternalStorage))
return
}
var addr oid.Address
addr.SetContainer(n.corsCnrInfo.CID)
sortedVersions := corsVersions.GetSorted()
for _, version := range sortedVersions {
addr.SetObject(version.ObjID)
n.deleteCORSObject(ctx, bktInfo, addr)
}
}
func checkCORS(cors *data.CORSConfiguration) error {
for _, r := range cors.CORSRules {
for _, m := range r.AllowedMethods {

View file

@ -11,6 +11,7 @@ import (
"strings"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
v2container "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/container"
@ -74,27 +75,34 @@ func (k *FeatureSettingsMock) FormContainerZone(ns string) string {
var _ frostfs.FrostFS = (*TestFrostFS)(nil)
type offsetError struct {
offset int
err error
}
type TestFrostFS struct {
objects map[string]*object.Object
copiesNumbers map[string][]uint32
objectErrors map[string]error
objectPutErrors map[string]error
containers map[string]*container.Container
chains map[string][]chain.Chain
currentEpoch uint64
key *keys.PrivateKey
tombstoneOIDCount int
objects map[string]*object.Object
copiesNumbers map[string][]uint32
objectErrors map[string]error
objectStreamErrors map[string]offsetError
objectPutErrors map[string]error
containers map[string]*container.Container
chains map[string][]chain.Chain
currentEpoch uint64
key *keys.PrivateKey
tombstoneOIDCount int
}
func NewTestFrostFS(key *keys.PrivateKey) *TestFrostFS {
return &TestFrostFS{
objects: make(map[string]*object.Object),
copiesNumbers: make(map[string][]uint32),
objectErrors: make(map[string]error),
objectPutErrors: make(map[string]error),
containers: make(map[string]*container.Container),
chains: make(map[string][]chain.Chain),
key: key,
objects: make(map[string]*object.Object),
copiesNumbers: make(map[string][]uint32),
objectErrors: make(map[string]error),
objectStreamErrors: make(map[string]offsetError),
objectPutErrors: make(map[string]error),
containers: make(map[string]*container.Container),
chains: make(map[string][]chain.Chain),
key: key,
}
}
@ -110,6 +118,14 @@ func (t *TestFrostFS) SetObjectError(addr oid.Address, err error) {
}
}
func (t *TestFrostFS) SetObjectStreamError(addr oid.Address, offset int, err error) {
if err == nil {
delete(t.objectStreamErrors, addr.EncodeToString())
} else {
t.objectStreamErrors[addr.EncodeToString()] = offsetError{offset: offset, err: err}
}
}
func (t *TestFrostFS) SetObjectPutError(fileName string, err error) {
if err == nil {
delete(t.objectPutErrors, fileName)
@ -160,6 +176,34 @@ func (t *TestFrostFS) SetContainer(cnrID cid.ID, cnr *container.Container) {
t.containers[cnrID.EncodeToString()] = cnr
}
func (t *TestFrostFS) SetObject(addr oid.Address, obj *object.Object) {
t.objects[addr.EncodeToString()] = obj
}
func (t *TestFrostFS) AddCORSObject(bkt *data.BucketInfo, corsCnrID cid.ID, cors string) {
a := object.NewAttribute()
a.SetKey(object.AttributeFilePath)
a.SetValue(bkt.CORSObjectFilePath())
var owner user.ID
user.IDFromKey(&owner, t.key.PrivateKey.PublicKey)
objID := oidtest.ID()
obj := object.New()
obj.SetContainerID(corsCnrID)
obj.SetID(objID)
obj.SetPayloadSize(uint64(len(cors)))
obj.SetPayload([]byte(cors))
obj.SetAttributes(*a)
obj.SetCreationEpoch(t.currentEpoch)
obj.SetOwnerID(owner)
t.currentEpoch++
addr := newAddress(corsCnrID, objID)
t.objects[addr.EncodeToString()] = obj
}
func (t *TestFrostFS) CreateContainer(_ context.Context, prm frostfs.PrmContainerCreate) (*frostfs.ContainerCreateResult, error) {
var cnr container.Container
cnr.Init()
@ -261,11 +305,33 @@ func (t *TestFrostFS) GetObject(ctx context.Context, prm frostfs.PrmObjectGet) (
}
return &frostfs.Object{
Header: *obj,
Payload: io.NopCloser(bytes.NewReader(obj.Payload())),
Header: *obj,
Payload: &objPayload{
r: bytes.NewReader(obj.Payload()),
offsetErr: t.objectStreamErrors[prm.Container.EncodeToString()+"/"+prm.Object.EncodeToString()],
},
}, nil
}
type objPayload struct {
offset int
r io.Reader
offsetErr offsetError
}
func (o *objPayload) Read(p []byte) (n int, err error) {
n, err = o.r.Read(p)
if o.offsetErr.err != nil && o.offset+n > o.offsetErr.offset {
return o.offsetErr.offset - o.offset, o.offsetErr.err
}
o.offset += n
return n, err
}
func (o *objPayload) Close() error {
return nil
}
func (t *TestFrostFS) RangeObject(ctx context.Context, prm frostfs.PrmObjectRange) (io.ReadCloser, error) {
obj, err := t.retrieveObject(ctx, prm.Container, prm.Object)
if err != nil {

View file

@ -15,6 +15,7 @@ import (
"strings"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
@ -193,6 +194,19 @@ type (
Prefix string
VersionIDMarker string
Encode string
Chan chan<- struct{}
}
ListBucketsParams struct {
MaxBuckets int
Prefix string
ContinuationToken string
BucketRegion string
}
ListBucketsResult struct {
Containers []*data.BucketInfo
ContinuationToken string
}
// VersionedObject stores info about objects to delete.
@ -324,6 +338,9 @@ func (n *Layer) prepareAuthParameters(ctx context.Context, prm *frostfs.PrmAuth,
// GetBucketInfo returns bucket info by name.
func (n *Layer) GetBucketInfo(ctx context.Context, name string) (*data.BucketInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.GetBucketInfo")
defer span.End()
name, err := url.QueryUnescape(name)
if err != nil {
return nil, fmt.Errorf("unescape bucket name: %w", err)
@ -371,12 +388,34 @@ func (n *Layer) ResolveCID(ctx context.Context, name string) (cid.ID, error) {
// ListBuckets returns all user containers. The name of the bucket is a container
// id. Timestamp is omitted since it is not saved in frostfs container.
func (n *Layer) ListBuckets(ctx context.Context) ([]*data.BucketInfo, error) {
return n.containerList(ctx)
func (n *Layer) ListBuckets(ctx context.Context, params ListBucketsParams) (ListBucketsResult, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.ListBuckets")
defer span.End()
var result ListBucketsResult
var err error
if params.MaxBuckets == 0 {
return result, nil
}
result.Containers, err = n.containerList(ctx, params)
if err != nil {
return ListBucketsResult{}, err
}
if len(result.Containers) > params.MaxBuckets {
result.ContinuationToken = result.Containers[params.MaxBuckets].Name
result.Containers = result.Containers[:params.MaxBuckets]
}
return result, nil
}
// GetObject from storage.
func (n *Layer) GetObject(ctx context.Context, p *GetObjectParams) (*ObjectPayload, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.GetObject")
defer span.End()
var params getParams
params.objInfo = p.ObjectInfo
@ -491,6 +530,9 @@ func getDecrypter(p *GetObjectParams) (*encryption.Decrypter, error) {
// GetObjectInfo returns meta information about the object.
func (n *Layer) GetObjectInfo(ctx context.Context, p *HeadObjectParams) (*data.ObjectInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.GetObjectInfo")
defer span.End()
extendedObjectInfo, err := n.GetExtendedObjectInfo(ctx, p)
if err != nil {
return nil, err
@ -501,8 +543,13 @@ func (n *Layer) GetObjectInfo(ctx context.Context, p *HeadObjectParams) (*data.O
// GetExtendedObjectInfo returns meta information and corresponding info from the tree service about the object.
func (n *Layer) GetExtendedObjectInfo(ctx context.Context, p *HeadObjectParams) (*data.ExtendedObjectInfo, error) {
var objInfo *data.ExtendedObjectInfo
var err error
ctx, span := tracing.StartSpanFromContext(ctx, "layer.GetExtendedObjectInfo")
defer span.End()
var (
objInfo *data.ExtendedObjectInfo
err error
)
if p.Versioned() {
objInfo, err = n.headVersion(ctx, p.BktInfo, p)
@ -515,13 +562,18 @@ func (n *Layer) GetExtendedObjectInfo(ctx context.Context, p *HeadObjectParams)
n.reqLogger(ctx).Debug(logs.GetObject,
zap.Stringer("cid", p.BktInfo.CID),
zap.Stringer("oid", objInfo.ObjectInfo.ID))
zap.Stringer("oid", objInfo.ObjectInfo.ID),
logs.TagField(logs.TagDatapath),
)
return objInfo, nil
}
// CopyObject from one bucket into another bucket.
func (n *Layer) CopyObject(ctx context.Context, p *CopyObjectParams) (*data.ExtendedObjectInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.CopyObject")
defer span.End()
objPayload, err := n.GetObject(ctx, &GetObjectParams{
ObjectInfo: p.SrcObject,
Versioned: p.SrcVersioned,
@ -568,8 +620,8 @@ func (n *Layer) deleteObject(ctx context.Context, bkt *data.BucketInfo, settings
if !client.IsErrObjectAlreadyRemoved(obj.Error) && !client.IsErrObjectNotFound(obj.Error) {
return obj
}
n.reqLogger(ctx).Debug(logs.CouldntDeleteObjectFromStorageContinueDeleting,
zap.Stringer("cid", bkt.CID), zap.String("oid", obj.VersionID), zap.Error(obj.Error))
n.reqLogger(ctx).Debug(logs.CouldntDeleteObjectFromStorageContinueDeleting, zap.Stringer("cid", bkt.CID),
zap.String("oid", obj.VersionID), zap.Error(obj.Error), logs.TagField(logs.TagExternalStorage))
}
if obj.Error = n.treeService.RemoveVersion(ctx, bkt, nodeVersion.ID); obj.Error != nil {
@ -607,8 +659,8 @@ func (n *Layer) deleteObject(ctx context.Context, bkt *data.BucketInfo, settings
if !client.IsErrObjectAlreadyRemoved(obj.Error) && !client.IsErrObjectNotFound(obj.Error) {
return obj
}
n.reqLogger(ctx).Debug(logs.CouldntDeleteObjectFromStorageContinueDeleting,
zap.Stringer("cid", bkt.CID), zap.String("oid", obj.VersionID), zap.Error(obj.Error))
n.reqLogger(ctx).Debug(logs.CouldntDeleteObjectFromStorageContinueDeleting, zap.Stringer("cid", bkt.CID),
zap.String("oid", obj.VersionID), zap.Error(obj.Error), logs.TagField(logs.TagExternalStorage))
}
}
@ -723,7 +775,7 @@ func (n *Layer) getNodeVersionsToDelete(ctx context.Context, bkt *data.BucketInf
return nil, fmt.Errorf("%w: there isn't tree node with requested version id", apierr.GetAPIError(apierr.ErrNoSuchVersion))
}
n.reqLogger(ctx).Debug(logs.GetTreeNodeToDelete, zap.Stringer("cid", bkt.CID), zap.Strings("oids", oids))
n.reqLogger(ctx).Debug(logs.GetTreeNodeToDelete, zap.Stringer("cid", bkt.CID), zap.Strings("oids", oids), logs.TagField(logs.TagDatapath))
return versionsToDelete, nil
}
@ -790,10 +842,13 @@ func (n *Layer) removeCombinedObject(ctx context.Context, bkt *data.BucketInfo,
// DeleteObjects from the storage.
func (n *Layer) DeleteObjects(ctx context.Context, p *DeleteObjectParams) []*VersionedObject {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.DeleteObjects")
defer span.End()
for i, obj := range p.Objects {
p.Objects[i] = n.deleteObject(ctx, p.BktInfo, p.Settings, obj, p.NetworkInfo)
if p.IsMultiple && p.Objects[i].Error != nil {
n.reqLogger(ctx).Error(logs.CouldntDeleteObject, zap.String("object", obj.String()), zap.Error(p.Objects[i].Error))
n.reqLogger(ctx).Error(logs.CouldntDeleteObject, zap.String("object", obj.String()), zap.Error(p.Objects[i].Error), logs.TagField(logs.TagExternalStorage))
}
}
@ -801,6 +856,9 @@ func (n *Layer) DeleteObjects(ctx context.Context, p *DeleteObjectParams) []*Ver
}
func (n *Layer) CreateBucket(ctx context.Context, p *CreateBucketParams) (*data.BucketInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.CreateBucket")
defer span.End()
bktInfo, err := n.GetBucketInfo(ctx, p.Name)
if err != nil {
if apierr.IsS3Error(err, apierr.ErrNoSuchBucket) {
@ -823,13 +881,16 @@ func (n *Layer) ResolveBucket(ctx context.Context, zone, name string) (cid.ID, e
return cid.ID{}, err
}
n.reqLogger(ctx).Info(logs.ResolveBucket, zap.Stringer("cid", cnrID))
n.reqLogger(ctx).Info(logs.ResolveBucket, zap.Stringer("cid", cnrID), logs.TagField(logs.TagDatapath))
}
return cnrID, nil
}
func (n *Layer) DeleteBucket(ctx context.Context, p *DeleteBucketParams) error {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.DeleteBucket")
defer span.End()
if !p.SkipCheck {
res, _, err := n.getAllObjectsVersions(ctx, commonVersionsListingParams{
BktInfo: p.BktInfo,
@ -846,14 +907,14 @@ func (n *Layer) DeleteBucket(ctx context.Context, p *DeleteBucketParams) error {
n.cache.DeleteBucket(p.BktInfo)
corsObj, err := n.treeService.GetBucketCORS(ctx, p.BktInfo)
corsObjs, err := n.treeService.GetAllBucketCORS(ctx, p.BktInfo)
if err != nil {
n.reqLogger(ctx).Error(logs.GetBucketCors, zap.Error(err))
n.reqLogger(ctx).Error(logs.GetAllBucketCorsFromTree, zap.Error(err), logs.TagField(logs.TagExternalStorageTree))
}
lifecycleObj, treeErr := n.treeService.GetBucketLifecycleConfiguration(ctx, p.BktInfo)
if treeErr != nil {
n.reqLogger(ctx).Error(logs.GetBucketLifecycle, zap.Error(treeErr))
n.reqLogger(ctx).Error(logs.GetBucketLifecycle, zap.Error(treeErr), logs.TagField(logs.TagExternalStorageTree))
}
err = n.frostFS.DeleteContainer(ctx, p.BktInfo.CID, p.SessionToken)
@ -861,10 +922,14 @@ func (n *Layer) DeleteBucket(ctx context.Context, p *DeleteBucketParams) error {
return fmt.Errorf("delete container: %w", err)
}
if !corsObj.Container().Equals(p.BktInfo.CID) && !corsObj.Container().Equals(cid.ID{}) {
n.deleteCORSObject(ctx, p.BktInfo, corsObj)
for _, corsObj := range corsObjs {
if !corsObj.Container().Equals(p.BktInfo.CID) && !corsObj.Container().Equals(cid.ID{}) {
n.deleteCORSObject(ctx, p.BktInfo, corsObj)
}
}
n.deleteCORSVersions(ctx, p.BktInfo)
if treeErr == nil && !lifecycleObj.Container().Equals(p.BktInfo.CID) {
n.deleteLifecycleObject(ctx, p.BktInfo, lifecycleObj)
}
@ -873,6 +938,9 @@ func (n *Layer) DeleteBucket(ctx context.Context, p *DeleteBucketParams) error {
}
func (n *Layer) DeleteContainer(ctx context.Context, p *DeleteBucketParams) error {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.DeleteContainer")
defer span.End()
n.cache.DeleteBucket(p.BktInfo)
if err := n.frostFS.DeleteContainer(ctx, p.BktInfo.CID, p.SessionToken); err != nil {
return fmt.Errorf("delete container: %w", err)
@ -881,6 +949,9 @@ func (n *Layer) DeleteContainer(ctx context.Context, p *DeleteBucketParams) erro
}
func (n *Layer) GetNetworkInfo(ctx context.Context) (netmap.NetworkInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.GetNetworkInfo")
defer span.End()
cachedInfo := n.cache.GetNetworkInfo()
if cachedInfo != nil {
return *cachedInfo, nil

View file

@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
@ -23,6 +24,9 @@ type PutBucketLifecycleParams struct {
}
func (n *Layer) PutBucketLifecycleConfiguration(ctx context.Context, p *PutBucketLifecycleParams) error {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.PutBucketLifecycleConfiguration")
defer span.End()
cfgBytes, err := xml.Marshal(p.LifecycleCfg)
if err != nil {
return fmt.Errorf("marshal lifecycle configuration: %w", err)
@ -79,11 +83,16 @@ func (n *Layer) deleteLifecycleObject(ctx context.Context, bktInfo *data.BucketI
if err := n.objectDeleteWithAuth(ctx, lifecycleBkt, addr.Object(), prmAuth); err != nil {
n.reqLogger(ctx).Error(logs.CouldntDeleteLifecycleObject, zap.Error(err),
zap.String("cid", lifecycleBkt.CID.EncodeToString()),
zap.String("oid", addr.Object().EncodeToString()))
zap.String("oid", addr.Object().EncodeToString()),
logs.TagField(logs.TagExternalStorage),
)
}
}
func (n *Layer) GetBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo) (*data.LifecycleConfiguration, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.GetBucketLifecycleConfiguration")
defer span.End()
owner := n.BearerOwner(ctx)
if cfg := n.cache.GetLifecycleConfiguration(owner, bktInfo); cfg != nil {
return cfg, nil
@ -129,6 +138,9 @@ func (n *Layer) GetBucketLifecycleConfiguration(ctx context.Context, bktInfo *da
}
func (n *Layer) DeleteBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo) error {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.DeleteBucketLifecycleConfiguration")
defer span.End()
objs, err := n.treeService.DeleteBucketLifecycleConfiguration(ctx, bktInfo)
objsNotFound := errors.Is(err, tree.ErrNoNodeToRemove)
if err != nil && !objsNotFound {

View file

@ -9,6 +9,7 @@ import (
"strings"
"sync"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
@ -26,6 +27,7 @@ type (
Encode string
MaxKeys int
Prefix string
Chan chan<- struct{}
}
// ListObjectsParamsV1 contains params for ListObjectsV1.
@ -80,6 +82,8 @@ type (
MaxKeys int
Marker string
Bookmark string
// Chan is a channel to prevent client from context canceling during long listing.
Chan chan<- struct{}
}
commonLatestVersionsListingParams struct {
@ -97,6 +101,9 @@ const (
// ListObjectsV1 returns objects in a bucket for requests of Version 1.
func (n *Layer) ListObjectsV1(ctx context.Context, p *ListObjectsParamsV1) (*ListObjectsInfoV1, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.ListObjectsV1")
defer span.End()
var result ListObjectsInfoV1
prm := commonLatestVersionsListingParams{
@ -107,6 +114,7 @@ func (n *Layer) ListObjectsV1(ctx context.Context, p *ListObjectsParamsV1) (*Lis
MaxKeys: p.MaxKeys,
Marker: p.Marker,
Bookmark: p.Marker,
Chan: p.Chan,
},
ListType: ListObjectsV1Type,
}
@ -128,6 +136,9 @@ func (n *Layer) ListObjectsV1(ctx context.Context, p *ListObjectsParamsV1) (*Lis
// ListObjectsV2 returns objects in a bucket for requests of Version 2.
func (n *Layer) ListObjectsV2(ctx context.Context, p *ListObjectsParamsV2) (*ListObjectsInfoV2, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.ListObjectsV2")
defer span.End()
var result ListObjectsInfoV2
prm := commonLatestVersionsListingParams{
@ -138,6 +149,7 @@ func (n *Layer) ListObjectsV2(ctx context.Context, p *ListObjectsParamsV2) (*Lis
MaxKeys: p.MaxKeys,
Marker: p.StartAfter,
Bookmark: p.ContinuationToken,
Chan: p.Chan,
},
ListType: ListObjectsV2Type,
}
@ -158,6 +170,9 @@ func (n *Layer) ListObjectsV2(ctx context.Context, p *ListObjectsParamsV2) (*Lis
}
func (n *Layer) ListObjectVersions(ctx context.Context, p *ListObjectVersionsParams) (*ListObjectVersionsInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.ListObjectVersions")
defer span.End()
prm := commonVersionsListingParams{
BktInfo: p.BktInfo,
Delimiter: p.Delimiter,
@ -165,6 +180,7 @@ func (n *Layer) ListObjectVersions(ctx context.Context, p *ListObjectVersionsPar
MaxKeys: p.MaxKeys,
Marker: p.KeyMarker,
Bookmark: p.VersionIDMarker,
Chan: p.Chan,
}
objects, isTruncated, err := n.getAllObjectsVersions(ctx, prm)
@ -208,6 +224,10 @@ func (n *Layer) getLatestObjectsVersions(ctx context.Context, p commonLatestVers
objects = append(objects, session.Next...)
for obj := range objOutCh {
objects = append(objects, obj)
select {
case p.Chan <- struct{}{}:
default:
}
}
if err = <-errorCh; err != nil {
@ -277,6 +297,11 @@ func handleGeneratedVersions(objOutCh <-chan *data.ExtendedNodeVersion, p common
allObjects = append(allObjects, eoi)
}
lastName = name
select {
case p.Chan <- struct{}{}:
default:
}
}
formVersionsListRow(allObjects, listRowStartIndex, session)
@ -541,7 +566,7 @@ func (n *Layer) initWorkerPool(ctx context.Context, size int, p commonVersionsLi
realSize, err := GetObjectSize(oi)
if err != nil {
reqLog.Debug(logs.FailedToGetRealObjectSize, zap.Error(err))
reqLog.Debug(logs.FailedToGetRealObjectSize, zap.Error(err), logs.TagField(logs.TagDatapath))
realSize = oi.Size
}
@ -554,7 +579,7 @@ func (n *Layer) initWorkerPool(ctx context.Context, size int, p commonVersionsLi
})
if err != nil {
wg.Done()
reqLog.Warn(logs.FailedToSubmitTaskToPool, zap.Error(err))
reqLog.Warn(logs.FailedToSubmitTaskToPool, zap.Error(err), logs.TagField(logs.TagDatapath))
}
}(node)
}
@ -645,7 +670,7 @@ func (n *Layer) objectInfoFromObjectsCacheOrFrostFS(ctx context.Context, bktInfo
meta, err := n.objectHead(ctx, bktInfo, node.OID)
if err != nil {
n.reqLogger(ctx).Warn(logs.CouldNotFetchObjectMeta, zap.Error(err))
n.reqLogger(ctx).Warn(logs.CouldNotFetchObjectMeta, zap.Error(err), logs.TagField(logs.TagExternalStorage))
return nil
}

View file

@ -15,6 +15,7 @@ import (
"strings"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
@ -151,6 +152,9 @@ type (
)
func (n *Layer) CreateMultipartUpload(ctx context.Context, p *CreateMultipartParams) error {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.CreateMultipartUpload")
defer span.End()
metaSize := len(p.Header)
if p.Data != nil {
metaSize += len(p.Data.TagSet)
@ -190,6 +194,9 @@ func (n *Layer) CreateMultipartUpload(ctx context.Context, p *CreateMultipartPar
}
func (n *Layer) UploadPart(ctx context.Context, p *UploadPartParams) (string, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.UploadPart")
defer span.End()
multipartInfo, err := n.treeService.GetMultipartUpload(ctx, p.Info.Bkt, p.Info.Key, p.Info.UploadID)
if err != nil {
if errors.Is(err, tree.ErrNodeNotFound) {
@ -213,7 +220,7 @@ func (n *Layer) UploadPart(ctx context.Context, p *UploadPartParams) (string, er
func (n *Layer) uploadPart(ctx context.Context, multipartInfo *data.MultipartInfo, p *UploadPartParams) (*data.ObjectInfo, error) {
encInfo := FormEncryptionInfo(multipartInfo.Meta)
if err := p.Info.Encryption.MatchObjectEncryption(encInfo); err != nil {
n.reqLogger(ctx).Warn(logs.MismatchedObjEncryptionInfo, zap.Error(err))
n.reqLogger(ctx).Warn(logs.MismatchedObjEncryptionInfo, zap.Error(err), logs.TagField(logs.TagDatapath))
return nil, apierr.GetAPIError(apierr.ErrInvalidEncryptionParameters)
}
@ -266,7 +273,10 @@ func (n *Layer) uploadPart(ctx context.Context, multipartInfo *data.MultipartInf
n.prepareAuthParameters(ctx, &prm.PrmAuth, bktInfo.Owner)
err = n.frostFS.DeleteObject(ctx, prm)
if err != nil {
n.reqLogger(ctx).Debug(logs.FailedToDeleteObject, zap.Stringer("cid", bktInfo.CID), zap.Stringer("oid", createdObj.ID))
n.reqLogger(ctx).Debug(logs.FailedToDeleteObject,
zap.Stringer("cid", bktInfo.CID),
zap.Stringer("oid", createdObj.ID),
logs.TagField(logs.TagExternalStorage))
}
return nil, apierr.GetAPIError(apierr.ErrInvalidDigest)
}
@ -283,7 +293,10 @@ func (n *Layer) uploadPart(ctx context.Context, multipartInfo *data.MultipartInf
if !bytes.Equal(contentHashBytes, createdObj.HashSum) {
err = n.objectDelete(ctx, bktInfo, createdObj.ID)
if err != nil {
n.reqLogger(ctx).Debug(logs.FailedToDeleteObject, zap.Stringer("cid", bktInfo.CID), zap.Stringer("oid", createdObj.ID))
n.reqLogger(ctx).Debug(logs.FailedToDeleteObject,
zap.Stringer("cid", bktInfo.CID),
zap.Stringer("oid", createdObj.ID),
logs.TagField(logs.TagExternalStorage))
}
return nil, apierr.GetAPIError(apierr.ErrContentSHA256Mismatch)
}
@ -291,7 +304,7 @@ func (n *Layer) uploadPart(ctx context.Context, multipartInfo *data.MultipartInf
n.reqLogger(ctx).Debug(logs.UploadPart,
zap.String("multipart upload", p.Info.UploadID), zap.Int("part number", p.PartNumber),
zap.Stringer("cid", bktInfo.CID), zap.Stringer("oid", createdObj.ID))
zap.Stringer("cid", bktInfo.CID), zap.Stringer("oid", createdObj.ID), logs.TagField(logs.TagDatapath))
partInfo := &data.PartInfo{
Key: p.Info.Key,
@ -314,7 +327,8 @@ func (n *Layer) uploadPart(ctx context.Context, multipartInfo *data.MultipartInf
if err = n.objectDelete(ctx, bktInfo, oldPartID); err != nil {
n.reqLogger(ctx).Error(logs.CouldntDeleteOldPartObject, zap.Error(err),
zap.String("cid", bktInfo.CID.EncodeToString()),
zap.String("oid", oldPartID.EncodeToString()))
zap.String("oid", oldPartID.EncodeToString()),
logs.TagField(logs.TagExternalStorage))
}
}
}
@ -335,6 +349,9 @@ func (n *Layer) uploadPart(ctx context.Context, multipartInfo *data.MultipartInf
}
func (n *Layer) UploadPartCopy(ctx context.Context, p *UploadCopyParams) (*data.ObjectInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.UploadPartCopy")
defer span.End()
multipartInfo, err := n.treeService.GetMultipartUpload(ctx, p.Info.Bkt, p.Info.Key, p.Info.UploadID)
if err != nil {
if errors.Is(err, tree.ErrNodeNotFound) {
@ -384,6 +401,9 @@ func (n *Layer) UploadPartCopy(ctx context.Context, p *UploadCopyParams) (*data.
}
func (n *Layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipartParams) (*UploadData, *data.ExtendedObjectInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.CompleteMultipartUpload")
defer span.End()
for i := 1; i < len(p.Parts); i++ {
if p.Parts[i].PartNumber <= p.Parts[i-1].PartNumber {
return nil, nil, apierr.GetAPIError(apierr.ErrInvalidPartOrder)
@ -481,7 +501,8 @@ func (n *Layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipar
n.reqLogger(ctx).Error(logs.CouldNotPutCompletedObject,
zap.String("uploadID", p.Info.UploadID),
zap.String("uploadKey", p.Info.Key),
zap.Error(err))
zap.Error(err),
logs.TagField(logs.TagExternalStorage))
return nil, nil, apierr.GetAPIError(apierr.ErrInternalError)
}
@ -493,7 +514,8 @@ func (n *Layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipar
if err = n.objectDelete(ctx, p.Info.Bkt, partInfo.OID); err != nil {
n.reqLogger(ctx).Warn(logs.CouldNotDeleteUploadPart,
zap.Stringer("cid", p.Info.Bkt.CID), zap.Stringer("oid", &partInfo.OID),
zap.Error(err))
zap.Error(err),
logs.TagField(logs.TagExternalStorage))
}
addr.SetObject(partInfo.OID)
n.cache.DeleteObject(addr)
@ -504,6 +526,9 @@ func (n *Layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipar
}
func (n *Layer) ListMultipartUploads(ctx context.Context, p *ListMultipartUploadsParams) (*ListMultipartUploadsInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.ListMultipartUploads")
defer span.End()
var result ListMultipartUploadsInfo
if p.MaxUploads == 0 {
return &result, nil
@ -564,6 +589,9 @@ func (n *Layer) ListMultipartUploads(ctx context.Context, p *ListMultipartUpload
}
func (n *Layer) AbortMultipartUpload(ctx context.Context, p *UploadInfoParams) error {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.AbortMultipartUpload")
defer span.End()
multipartInfo, parts, err := n.getUploadParts(ctx, p)
if err != nil {
return err
@ -587,7 +615,7 @@ func (n *Layer) deleteUploadedParts(ctx context.Context, bkt *data.BucketInfo, p
oids, err := relations.ListAllRelations(ctx, n.frostFS.Relations(), bkt.CID, info.OID, tokens)
if err != nil {
n.reqLogger(ctx).Warn(logs.FailedToListAllObjectRelations, zap.String("cid", bkt.CID.EncodeToString()),
zap.String("oid", info.OID.EncodeToString()), zap.Error(err))
zap.String("oid", info.OID.EncodeToString()), zap.Error(err), logs.TagField(logs.TagExternalStorage))
continue
}
members = append(members, append(oids, info.OID)...)
@ -596,11 +624,14 @@ func (n *Layer) deleteUploadedParts(ctx context.Context, bkt *data.BucketInfo, p
err := n.putTombstones(ctx, bkt, networkInfo, members)
if err != nil {
n.reqLogger(ctx).Warn(logs.FailedToPutTombstones, zap.Error(err))
n.reqLogger(ctx).Warn(logs.FailedToPutTombstones, zap.Error(err), logs.TagField(logs.TagExternalStorage))
}
}
func (n *Layer) ListParts(ctx context.Context, p *ListPartsParams) (*ListPartsInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.ListParts")
defer span.End()
var res ListPartsInfo
multipartInfo, partsInfo, err := n.getUploadParts(ctx, p.Info)
if err != nil {
@ -609,7 +640,7 @@ func (n *Layer) ListParts(ctx context.Context, p *ListPartsParams) (*ListPartsIn
encInfo := FormEncryptionInfo(multipartInfo.Meta)
if err = p.Info.Encryption.MatchObjectEncryption(encInfo); err != nil {
n.reqLogger(ctx).Warn(logs.MismatchedObjEncryptionInfo, zap.Error(err))
n.reqLogger(ctx).Warn(logs.MismatchedObjEncryptionInfo, zap.Error(err), logs.TagField(logs.TagDatapath))
return nil, apierr.GetAPIError(apierr.ErrInvalidEncryptionParameters)
}
@ -701,7 +732,8 @@ func (n *Layer) getUploadParts(ctx context.Context, p *UploadInfoParams) (*data.
zap.Stringer("cid", p.Bkt.CID),
zap.String("upload id", p.UploadID),
zap.Ints("part numbers", partsNumbers),
zap.Strings("oids", oids))
zap.Strings("oids", oids),
logs.TagField(logs.TagDatapath))
return multipartInfo, res, nil
}

View file

@ -17,6 +17,7 @@ import (
"strconv"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
@ -227,6 +228,9 @@ func ParseCompletedPartHeader(hdr string) (*Part, error) {
// PutObject stores object into FrostFS, took payload from io.Reader.
func (n *Layer) PutObject(ctx context.Context, p *PutObjectParams) (*data.ExtendedObjectInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.PutObject")
defer span.End()
bktSettings, err := n.GetBucketSettings(ctx, p.BktInfo)
if err != nil {
return nil, fmt.Errorf("couldn't get versioning settings object: %w", err)
@ -295,7 +299,11 @@ func (n *Layer) PutObject(ctx context.Context, p *PutObjectParams) (*data.Extend
if !bytes.Equal(headerMd5Hash, createdObj.MD5Sum) {
err = n.objectDelete(ctx, p.BktInfo, createdObj.ID)
if err != nil {
n.reqLogger(ctx).Debug(logs.FailedToDeleteObject, zap.Stringer("cid", p.BktInfo.CID), zap.Stringer("oid", createdObj.ID))
n.reqLogger(ctx).Debug(logs.FailedToDeleteObject,
zap.Stringer("cid", p.BktInfo.CID),
zap.Stringer("oid", createdObj.ID),
logs.TagField(logs.TagExternalStorage),
)
}
return nil, apierr.GetAPIError(apierr.ErrBadDigest)
}
@ -309,13 +317,17 @@ func (n *Layer) PutObject(ctx context.Context, p *PutObjectParams) (*data.Extend
if !bytes.Equal(contentHashBytes, createdObj.HashSum) {
err = n.objectDelete(ctx, p.BktInfo, createdObj.ID)
if err != nil {
n.reqLogger(ctx).Debug(logs.FailedToDeleteObject, zap.Stringer("cid", p.BktInfo.CID), zap.Stringer("oid", createdObj.ID))
n.reqLogger(ctx).Debug(logs.FailedToDeleteObject,
zap.Stringer("cid", p.BktInfo.CID),
zap.Stringer("oid", createdObj.ID),
logs.TagField(logs.TagExternalStorage))
}
return nil, apierr.GetAPIError(apierr.ErrContentSHA256Mismatch)
}
}
n.reqLogger(ctx).Debug(logs.PutObject, zap.Stringer("cid", p.BktInfo.CID), zap.Stringer("oid", createdObj.ID))
n.reqLogger(ctx).Debug(logs.PutObject, zap.Stringer("cid", p.BktInfo.CID),
zap.Stringer("oid", createdObj.ID), logs.TagField(logs.TagExternalStorage))
now := TimeNow(ctx)
newVersion := &data.NodeVersion{
BaseNodeVersion: data.BaseNodeVersion{
@ -540,7 +552,7 @@ func (n *Layer) objectPutAndHash(ctx context.Context, prm frostfs.PrmObjectCreat
func (n *Layer) payloadDiscard(ctx context.Context, payload io.Reader) {
if payload != nil {
if _, errDiscard := io.Copy(io.Discard, payload); errDiscard != nil {
n.reqLogger(ctx).Warn(logs.FailedToDiscardPutPayloadProbablyGoroutineLeaks, zap.Error(errDiscard))
n.reqLogger(ctx).Warn(logs.FailedToDiscardPutPayloadProbablyGoroutineLeaks, zap.Error(errDiscard), logs.TagField(logs.TagDatapath))
}
}
}
@ -550,7 +562,7 @@ type logWrapper struct {
}
func (l *logWrapper) Printf(format string, args ...interface{}) {
l.log.Info(fmt.Sprintf(format, args...))
l.log.Info(fmt.Sprintf(format, args...), logs.TagField(logs.TagDatapath))
}
func IsSystemHeader(key string) bool {

View file

@ -10,6 +10,7 @@ import (
"strconv"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
@ -25,6 +26,9 @@ type PatchObjectParams struct {
}
func (n *Layer) PatchObject(ctx context.Context, p *PatchObjectParams) (*data.ExtendedObjectInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.PatchObject")
defer span.End()
if p.Object.ObjectInfo.Headers[AttributeDecryptedSize] != "" {
return nil, fmt.Errorf("patch encrypted object")
}

View file

@ -10,13 +10,16 @@ import (
"strconv"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/tree"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/crdt"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object"
apiobject "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object"
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"
)
@ -32,6 +35,9 @@ type PutLockInfoParams struct {
}
func (n *Layer) PutLockInfo(ctx context.Context, p *PutLockInfoParams) (err error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.PutLockInfo")
defer span.End()
newLock := p.NewLock
versionNode := p.NodeVersion
// sometimes node version can be provided from executing context
@ -139,6 +145,9 @@ func (n *Layer) putLockObject(ctx context.Context, bktInfo *data.BucketInfo, obj
}
func (n *Layer) GetLockInfo(ctx context.Context, objVersion *data.ObjectVersion) (*data.LockInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.GetLockInfo")
defer span.End()
owner := n.BearerOwner(ctx)
if lockInfo := n.cache.GetLockInfo(owner, lockObjectKey(objVersion)); lockInfo != nil {
return lockInfo, nil
@ -168,24 +177,42 @@ func (n *Layer) getCORS(ctx context.Context, bkt *data.BucketInfo, decoder func(
return cors, nil
}
addr, err := n.treeService.GetBucketCORS(ctx, bkt)
objNotFound := errors.Is(err, tree.ErrNodeNotFound)
if err != nil && !objNotFound {
corsVersions, err := n.getCORSVersions(ctx, bkt)
if err != nil {
return nil, err
}
if objNotFound {
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchCORSConfiguration), err.Error())
}
var (
prmAuth frostfs.PrmAuth
objID oid.ID
corsBkt = bkt
lastCORS = corsVersions.GetLast()
)
var prmAuth frostfs.PrmAuth
corsBkt := bkt
if !addr.Container().Equals(bkt.CID) && !addr.Container().Equals(cid.ID{}) {
corsBkt = &data.BucketInfo{CID: addr.Container()}
if lastCORS != nil {
prmAuth.PrivateKey = &n.gateKey.PrivateKey
corsBkt = n.corsCnrInfo
objID = lastCORS.ObjID
} else {
addr, err := n.treeService.GetBucketCORS(ctx, bkt)
objNotFound := errors.Is(err, tree.ErrNodeNotFound)
if err != nil && !objNotFound {
return nil, err
}
if objNotFound {
return nil, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrNoSuchCORSConfiguration), err.Error())
}
if !addr.Container().Equals(bkt.CID) && !addr.Container().Equals(cid.ID{}) {
corsBkt = &data.BucketInfo{CID: addr.Container()}
prmAuth.PrivateKey = &n.gateKey.PrivateKey
}
objID = addr.Object()
}
obj, err := n.objectGetWithAuth(ctx, corsBkt, addr.Object(), prmAuth)
obj, err := n.objectGetWithAuth(ctx, corsBkt, objID, prmAuth)
if err != nil {
return nil, fmt.Errorf("get cors object: %w", err)
}
@ -200,12 +227,65 @@ func (n *Layer) getCORS(ctx context.Context, bkt *data.BucketInfo, decoder func(
return cors, nil
}
func (n *Layer) getCORSVersions(ctx context.Context, bkt *data.BucketInfo) (*crdt.ObjectVersions, error) {
corsVersions, err := n.frostFS.SearchObjects(ctx, frostfs.PrmObjectSearch{
Container: n.corsCnrInfo.CID,
ExactAttribute: [2]string{object.AttributeFilePath, bkt.CORSObjectFilePath()},
})
if err != nil {
return nil, fmt.Errorf("search cors objects: %w", err)
}
versions := crdt.NewObjectVersions(bkt.CORSObjectFilePath())
versions.SetLessFunc(func(ov1, ov2 *crdt.ObjectVersion) bool {
versionID1, versionID2 := ov1.VersionID(), ov2.VersionID()
timestamp1, timestamp2 := ov1.Headers[object.AttributeTimestamp], ov2.Headers[object.AttributeTimestamp]
if ov1.CreationEpoch != ov2.CreationEpoch {
return ov1.CreationEpoch < ov2.CreationEpoch
}
if len(timestamp1) > 0 && len(timestamp2) > 0 && timestamp1 != timestamp2 {
unixTime1, err := strconv.ParseInt(timestamp1, 10, 64)
if err != nil {
return versionID1 < versionID2
}
unixTime2, err := strconv.ParseInt(timestamp2, 10, 64)
if err != nil {
return versionID1 < versionID2
}
return unixTime1 < unixTime2
}
return versionID1 < versionID2
})
for _, id := range corsVersions {
objVersion, err := n.frostFS.HeadObject(ctx, frostfs.PrmObjectHead{
Container: n.corsCnrInfo.CID,
Object: id,
})
if err != nil {
return nil, fmt.Errorf("head cors object '%s': %w", id.EncodeToString(), err)
}
versions.AppendVersion(crdt.NewObjectVersion(objVersion))
}
return versions, nil
}
func lockObjectKey(objVersion *data.ObjectVersion) string {
// todo reconsider forming name since versionID can be "null" or ""
return ".lock." + objVersion.BktInfo.CID.EncodeToString() + "." + objVersion.ObjectName + "." + objVersion.VersionID
}
func (n *Layer) GetBucketSettings(ctx context.Context, bktInfo *data.BucketInfo) (*data.BucketSettings, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.GetBucketSettings")
defer span.End()
owner := n.BearerOwner(ctx)
if settings := n.cache.GetSettings(owner, bktInfo); settings != nil {
return settings, nil
@ -217,7 +297,7 @@ func (n *Layer) GetBucketSettings(ctx context.Context, bktInfo *data.BucketInfo)
return nil, err
}
settings = &data.BucketSettings{Versioning: data.VersioningUnversioned}
n.reqLogger(ctx).Debug(logs.BucketSettingsNotFoundUseDefaults)
n.reqLogger(ctx).Debug(logs.BucketSettingsNotFoundUseDefaults, logs.TagField(logs.TagDatapath))
}
n.cache.PutSettings(owner, bktInfo, settings)
@ -226,6 +306,9 @@ func (n *Layer) GetBucketSettings(ctx context.Context, bktInfo *data.BucketInfo)
}
func (n *Layer) PutBucketSettings(ctx context.Context, p *PutSettingsParams) error {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.PutBucketSettings")
defer span.End()
if err := n.treeService.PutSettingsNode(ctx, p.BktInfo, p.Settings); err != nil {
return fmt.Errorf("failed to get settings node: %w", err)
}
@ -261,7 +344,7 @@ func (n *Layer) attributesFromLock(ctx context.Context, lock *data.ObjectLock) (
if expEpoch != 0 {
result = append(result, [2]string{
object.SysAttributeExpEpoch, strconv.FormatUint(expEpoch, 10),
apiobject.SysAttributeExpEpoch, strconv.FormatUint(expEpoch, 10),
})
}

View file

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/tree"
@ -16,6 +17,9 @@ import (
)
func (n *Layer) GetObjectTagging(ctx context.Context, p *data.GetObjectTaggingParams) (string, map[string]string, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.GetObjectTagging")
defer span.End()
var err error
owner := n.BearerOwner(ctx)
@ -52,6 +56,9 @@ func (n *Layer) GetObjectTagging(ctx context.Context, p *data.GetObjectTaggingPa
}
func (n *Layer) PutObjectTagging(ctx context.Context, p *data.PutObjectTaggingParams) (err error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.PutObjectTagging")
defer span.End()
nodeVersion := p.NodeVersion
if nodeVersion == nil {
nodeVersion, err = n.getNodeVersionFromCacheOrFrostfs(ctx, p.ObjectVersion)
@ -75,6 +82,9 @@ func (n *Layer) PutObjectTagging(ctx context.Context, p *data.PutObjectTaggingPa
}
func (n *Layer) DeleteObjectTagging(ctx context.Context, p *data.ObjectVersion) error {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.DeleteObjectTagging")
defer span.End()
version, err := n.getNodeVersion(ctx, p)
if err != nil {
return err
@ -96,6 +106,9 @@ func (n *Layer) DeleteObjectTagging(ctx context.Context, p *data.ObjectVersion)
}
func (n *Layer) GetBucketTagging(ctx context.Context, bktInfo *data.BucketInfo) (map[string]string, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.GetBucketTagging")
defer span.End()
owner := n.BearerOwner(ctx)
if tags := n.cache.GetTagging(owner, bucketTaggingCacheKey(bktInfo.CID)); tags != nil {
@ -113,6 +126,9 @@ func (n *Layer) GetBucketTagging(ctx context.Context, bktInfo *data.BucketInfo)
}
func (n *Layer) PutBucketTagging(ctx context.Context, bktInfo *data.BucketInfo, tagSet map[string]string) error {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.PutBucketTagging")
defer span.End()
if err := n.treeService.PutBucketTagging(ctx, bktInfo, tagSet); err != nil {
return err
}
@ -123,6 +139,9 @@ func (n *Layer) PutBucketTagging(ctx context.Context, bktInfo *data.BucketInfo,
}
func (n *Layer) DeleteBucketTagging(ctx context.Context, bktInfo *data.BucketInfo) error {
ctx, span := tracing.StartSpanFromContext(ctx, "layer.DeleteBucketTagging")
defer span.End()
n.cache.DeleteTagging(bucketTaggingCacheKey(bktInfo.CID))
return n.treeService.DeleteBucketTagging(ctx, bktInfo)
@ -168,7 +187,8 @@ func (n *Layer) getNodeVersion(ctx context.Context, objVersion *data.ObjectVersi
if err == nil && version != nil && !version.IsDeleteMarker {
n.reqLogger(ctx).Debug(logs.GetTreeNode,
zap.Stringer("cid", objVersion.BktInfo.CID), zap.Stringer("oid", version.OID))
zap.Stringer("cid", objVersion.BktInfo.CID),
zap.Stringer("oid", version.OID), logs.TagField(logs.TagExternalStorageTree))
}
return version, err

View file

@ -65,13 +65,13 @@ func (n *Layer) submitPutTombstone(ctx context.Context, bkt *data.BucketInfo, me
defer wg.Done()
if err := n.putTombstoneObject(ctx, tomb, bkt); err != nil {
n.reqLogger(ctx).Warn(logs.FailedToPutTombstoneObject, zap.String("cid", bkt.CID.EncodeToString()), zap.Error(err))
n.reqLogger(ctx).Warn(logs.FailedToPutTombstoneObject, zap.String("cid", bkt.CID.EncodeToString()), zap.Error(err), logs.TagField(logs.TagExternalStorage))
errCh <- fmt.Errorf("put tombstone object: %w", err)
}
})
if err != nil {
wg.Done()
n.reqLogger(ctx).Warn(logs.FailedToSubmitTaskToPool, zap.Error(err))
n.reqLogger(ctx).Warn(logs.FailedToSubmitTaskToPool, zap.Error(err), logs.TagField(logs.TagDatapath))
errCh <- fmt.Errorf("submit task to pool: %w", err)
}
}
@ -106,7 +106,7 @@ func (n *Layer) getMembers(ctx context.Context, cnrID cid.ID, objID oid.ID, toke
}
n.reqLogger(ctx).Warn(logs.FailedToListAllObjectRelations, zap.String("cid", cnrID.EncodeToString()),
zap.String("oid", objID.EncodeToString()), zap.Error(err))
zap.String("oid", objID.EncodeToString()), zap.Error(err), logs.TagField(logs.TagExternalStorage))
return nil, nil
}
return append(oids, objID), nil

View file

@ -18,19 +18,19 @@ type Service interface {
// If tree node is not found returns ErrNodeNotFound error.
GetSettingsNode(ctx context.Context, bktInfo *data.BucketInfo) (*data.BucketSettings, error)
// GetBucketCORS gets an object id that corresponds to object with bucket CORS.
// GetBucketCORS gets an object address that corresponds to object with bucket CORS.
//
// If object id is not found returns ErrNodeNotFound error.
// If object is not found returns ErrNodeNotFound error.
GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.Address, error)
// PutBucketCORS puts a node to a system tree and returns objectID of a previous cors config which must be deleted in FrostFS.
// GetAllBucketCORS gets all object addresses that corresponds to objects with bucket CORS.
//
// If object ids to remove is not found returns ErrNoNodeToRemove error.
PutBucketCORS(ctx context.Context, bktInfo *data.BucketInfo, addr oid.Address) ([]oid.Address, error)
// If objects are not found returns ErrNodeNotFound error.
GetAllBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) ([]oid.Address, error)
// DeleteBucketCORS removes a node from a system tree and returns objID which must be deleted in FrostFS.
// DeleteBucketCORS removes a node from a system tree and returns object addresses which must be deleted in FrostFS.
//
// If object ids to remove is not found returns ErrNoNodeToRemove error.
// If objects to remove are not found returns ErrNoNodeToRemove error.
DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) ([]oid.Address, error)
GetObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion) (map[string]string, error)

View file

@ -115,12 +115,12 @@ func (t *TreeServiceMock) GetSettingsNode(_ context.Context, bktInfo *data.Bucke
func (t *TreeServiceMock) GetBucketCORS(_ context.Context, bktInfo *data.BucketInfo) (oid.Address, error) {
systemMap, ok := t.system[bktInfo.CID.EncodeToString()]
if !ok {
return oid.Address{}, nil
return oid.Address{}, tree.ErrNodeNotFound
}
node, ok := systemMap["cors"]
if !ok {
return oid.Address{}, nil
return oid.Address{}, tree.ErrNodeNotFound
}
var addr oid.Address
@ -129,19 +129,13 @@ func (t *TreeServiceMock) GetBucketCORS(_ context.Context, bktInfo *data.BucketI
return addr, nil
}
func (t *TreeServiceMock) PutBucketCORS(_ context.Context, bktInfo *data.BucketInfo, addr oid.Address) ([]oid.Address, error) {
systemMap, ok := t.system[bktInfo.CID.EncodeToString()]
if !ok {
systemMap = make(map[string]*data.BaseNodeVersion)
func (t *TreeServiceMock) GetAllBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) ([]oid.Address, error) {
cors, err := t.GetBucketCORS(ctx, bktInfo)
if err != nil {
return nil, err
}
systemMap["cors"] = &data.BaseNodeVersion{
OID: addr.Object(),
}
t.system[bktInfo.CID.EncodeToString()] = systemMap
return nil, tree.ErrNoNodeToRemove
return []oid.Address{cors}, nil
}
func (t *TreeServiceMock) DeleteBucketCORS(context.Context, *data.BucketInfo) ([]oid.Address, error) {

View file

@ -18,7 +18,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest"
)
func (tc *testContext) putObject(content []byte) *data.ObjectInfo {
@ -139,7 +139,7 @@ type testContext struct {
}
func prepareContext(t *testing.T, cachesConfig ...*CachesConfig) *testContext {
logger := zap.NewExample()
logger := zaptest.NewLogger(t)
key, err := keys.NewPrivateKey()
require.NoError(t, err)

View file

@ -110,7 +110,7 @@ func preparePathStyleAddress(reqInfo *ReqInfo, r *http.Request, reqLogger *zap.L
// https://github.com/go-chi/chi/issues/641
// https://github.com/go-chi/chi/issues/642
if obj, err := url.PathUnescape(reqInfo.ObjectName); err != nil {
reqLogger.Warn(logs.FailedToUnescapeObjectName, zap.Error(err))
reqLogger.Warn(logs.FailedToUnescapeObjectName, zap.Error(err), logs.TagField(logs.TagDatapath))
} else {
reqInfo.ObjectName = obj
}

View file

@ -1,12 +1,14 @@
package middleware
import (
"context"
"crypto/elliptic"
"errors"
"fmt"
"net/http"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
@ -30,7 +32,9 @@ type (
Center interface {
// Authenticate validate and authenticate request.
// Must return ErrNoAuthorizationHeader if auth header is missed.
Authenticate(request *http.Request) (*Box, error)
// Authenticate uses a separate context so that the authorization
// span middleware does not contain all subsequent spans.
Authenticate(ctx context.Context, request *http.Request) (*Box, error)
}
//nolint:revive
@ -47,34 +51,38 @@ var ErrNoAuthorizationHeader = errors.New("no authorization header")
func Auth(center Center, log *zap.Logger) Func {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
reqInfo := GetReqInfo(ctx)
reqCtx := r.Context()
ctx, span := tracing.StartSpanFromContext(reqCtx, "middleware.Auth")
reqInfo := GetReqInfo(reqCtx)
reqInfo.User = "anon"
box, err := center.Authenticate(r)
box, err := center.Authenticate(ctx, r)
if err != nil {
if errors.Is(err, ErrNoAuthorizationHeader) {
reqLogOrDefault(ctx, log).Debug(logs.CouldntReceiveAccessBoxForGateKeyRandomKeyWillBeUsed, zap.Error(err))
reqLogOrDefault(reqCtx, log).Debug(logs.CouldntReceiveAccessBoxForGateKeyRandomKeyWillBeUsed, zap.Error(err), logs.TagField(logs.TagDatapath))
} else {
reqLogOrDefault(ctx, log).Error(logs.FailedToPassAuthentication, zap.Error(err))
reqLogOrDefault(reqCtx, log).Error(logs.FailedToPassAuthentication, zap.Error(err), logs.TagField(logs.TagDatapath))
err = apierr.TransformToS3Error(err)
if err.(apierr.Error).ErrCode == apierr.ErrInternalError {
err = apierr.GetAPIError(apierr.ErrAccessDenied)
}
if _, wrErr := WriteErrorResponse(w, GetReqInfo(r.Context()), err); wrErr != nil {
reqLogOrDefault(ctx, log).Error(logs.FailedToWriteResponse, zap.Error(wrErr))
reqLogOrDefault(reqCtx, log).Error(logs.FailedToWriteResponse, zap.Error(wrErr), logs.TagField(logs.TagDatapath))
}
span.End()
return
}
} else {
ctx = SetBox(ctx, box)
reqCtx = SetBox(reqCtx, box)
if box.AccessBox.Gate.BearerToken != nil {
reqInfo.User = bearer.ResolveIssuer(*box.AccessBox.Gate.BearerToken).String()
}
reqLogOrDefault(ctx, log).Debug(logs.SuccessfulAuth, zap.String("accessKeyID", box.AuthHeaders.AccessKeyID))
reqLogOrDefault(reqCtx, log).Debug(logs.SuccessfulAuth, zap.String("accessKeyID", box.AuthHeaders.AccessKeyID), logs.TagField(logs.TagDatapath))
}
h.ServeHTTP(w, r.WithContext(ctx))
span.End()
h.ServeHTTP(w, r.WithContext(reqCtx))
})
}
}
@ -86,22 +94,26 @@ type FrostFSIDValidator interface {
func FrostfsIDValidation(frostfsID FrostFSIDValidator, log *zap.Logger) Func {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, span := tracing.StartSpanFromContext(r.Context(), "middleware.FrostfsIDValidation")
bd, err := GetBoxData(ctx)
if err != nil || bd.Gate.BearerToken == nil {
reqLogOrDefault(ctx, log).Debug(logs.AnonRequestSkipFrostfsIDValidation)
reqLogOrDefault(ctx, log).Debug(logs.AnonRequestSkipFrostfsIDValidation, logs.TagField(logs.TagDatapath))
span.End()
h.ServeHTTP(w, r)
return
}
if err = validateBearerToken(frostfsID, bd.Gate.BearerToken); err != nil {
reqLogOrDefault(ctx, log).Error(logs.FrostfsIDValidationFailed, zap.Error(err))
if _, wrErr := WriteErrorResponse(w, GetReqInfo(r.Context()), err); wrErr != nil {
reqLogOrDefault(ctx, log).Error(logs.FailedToWriteResponse, zap.Error(wrErr))
reqLogOrDefault(ctx, log).Error(logs.FrostfsIDValidationFailed, zap.Error(err), logs.TagField(logs.TagDatapath))
if _, wrErr := WriteErrorResponse(w, GetReqInfo(ctx), err); wrErr != nil {
reqLogOrDefault(ctx, log).Error(logs.FailedToWriteResponse, zap.Error(wrErr), logs.TagField(logs.TagDatapath))
}
span.End()
return
}
span.End()
h.ServeHTTP(w, r)
})
}

View file

@ -23,7 +23,7 @@ type (
)
func LogHTTP(l *zap.Logger, _ LogHTTPSettings) Func {
l.Warn(logs.LogHTTPDisabledInThisBuild)
l.Warn(logs.LogHTTPDisabledInThisBuild, logs.TagField(logs.TagApp))
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)

View file

@ -139,7 +139,7 @@ func resolveCID(log *zap.Logger, resolveContainerID ContainerIDResolveFunc) cidR
containerID, err := resolveContainerID(ctx, reqInfo.BucketName)
if err != nil {
reqLogOrDefault(ctx, log).Debug(logs.FailedToResolveCID, zap.Error(err))
reqLogOrDefault(ctx, log).Debug(logs.FailedToResolveCID, zap.Error(err), logs.TagField(logs.TagDatapath))
return ""
}

View file

@ -10,6 +10,7 @@ import (
"net/url"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
@ -88,32 +89,35 @@ type PolicyConfig struct {
func PolicyCheck(cfg PolicyConfig) Func {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := policyCheck(r, cfg); err != nil {
reqLogOrDefault(ctx, cfg.Log).Error(logs.PolicyValidationFailed, zap.Error(err))
ctx, span := tracing.StartSpanFromContext(r.Context(), "middleware.PolicyCheck")
if err := policyCheck(ctx, r, cfg); err != nil {
reqLogOrDefault(ctx, cfg.Log).Error(logs.PolicyValidationFailed, zap.Error(err), logs.TagField(logs.TagDatapath))
err = apierr.TransformToS3Error(err)
if _, wrErr := WriteErrorResponse(w, GetReqInfo(ctx), err); wrErr != nil {
reqLogOrDefault(ctx, cfg.Log).Error(logs.FailedToWriteResponse, zap.Error(wrErr))
reqLogOrDefault(ctx, cfg.Log).Error(logs.FailedToWriteResponse, zap.Error(wrErr), logs.TagField(logs.TagDatapath))
}
span.End()
return
}
span.End()
h.ServeHTTP(w, r)
})
}
}
func policyCheck(r *http.Request, cfg PolicyConfig) error {
reqInfo := GetReqInfo(r.Context())
func policyCheck(ctx context.Context, r *http.Request, cfg PolicyConfig) error {
reqInfo := GetReqInfo(ctx)
req, userKey, userGroups, err := getPolicyRequest(r, cfg, reqInfo.RequestType, reqInfo.BucketName, reqInfo.ObjectName)
req, userKey, userGroups, err := getPolicyRequest(ctx, r, cfg, reqInfo.RequestType, reqInfo.BucketName, reqInfo.ObjectName)
if err != nil {
return err
}
var bktInfo *data.BucketInfo
if reqInfo.RequestType != noneType && !strings.HasSuffix(req.Operation(), CreateBucketOperation) {
bktInfo, err = cfg.BucketResolver(r.Context(), reqInfo.BucketName)
bktInfo, err = cfg.BucketResolver(ctx, reqInfo.BucketName)
if err != nil {
return err
}
@ -161,7 +165,7 @@ func policyCheck(r *http.Request, cfg PolicyConfig) error {
return nil
}
func getPolicyRequest(r *http.Request, cfg PolicyConfig, reqType ReqType, bktName string, objName string) (*testutil.Request, *keys.PublicKey, []string, error) {
func getPolicyRequest(ctx context.Context, r *http.Request, cfg PolicyConfig, reqType ReqType, bktName string, objName string) (*testutil.Request, *keys.PublicKey, []string, error) {
var (
owner string
groups []string
@ -169,7 +173,6 @@ func getPolicyRequest(r *http.Request, cfg PolicyConfig, reqType ReqType, bktNam
pk *keys.PublicKey
)
ctx := r.Context()
bd, err := GetBoxData(ctx)
if err == nil && bd.Gate.BearerToken != nil {
pk, err = keys.NewPublicKeyFromBytes(bd.Gate.BearerToken.SigningKeyBytes(), elliptic.P256())
@ -193,14 +196,16 @@ func getPolicyRequest(r *http.Request, cfg PolicyConfig, reqType ReqType, bktNam
res = fmt.Sprintf(s3.ResourceFormatS3Bucket, bktName)
}
requestProps, resourceProps, err := determineProperties(r, cfg.Decoder, cfg.BucketResolver, cfg.Tagging, reqType, op, bktName, objName, owner, groups, tags)
requestProps, resourceProps, err := determineProperties(ctx, r, cfg.Decoder, cfg.BucketResolver, cfg.Tagging, reqType, op, bktName, objName, owner, groups, tags)
if err != nil {
return nil, nil, nil, fmt.Errorf("determine properties: %w", err)
}
reqLogOrDefault(r.Context(), cfg.Log).Debug(logs.PolicyRequest, zap.String("action", op),
zap.String("resource", res), zap.Any("request properties", requestProps),
zap.Any("resource properties", resourceProps))
zap.Any("resource properties", resourceProps),
logs.TagField(logs.TagDatapath),
)
return testutil.NewRequest(op, testutil.NewResource(res, resourceProps), requestProps), pk, groups, nil
}
@ -418,7 +423,7 @@ func determineGeneralOperation(r *http.Request) string {
return "UnmatchedOperation"
}
func determineProperties(r *http.Request, decoder XMLDecoder, resolver BucketResolveFunc, tagging ResourceTagging, reqType ReqType,
func determineProperties(ctx context.Context, r *http.Request, decoder XMLDecoder, resolver BucketResolveFunc, tagging ResourceTagging, reqType ReqType,
op, bktName, objName, owner string, groups []string, userClaims map[string]string) (requestProperties map[string]string, resourceProperties map[string]string, err error) {
requestProperties = map[string]string{
s3.PropertyKeyOwner: owner,
@ -466,7 +471,7 @@ func determineProperties(r *http.Request, decoder XMLDecoder, resolver BucketRes
requestProperties[k] = v
}
resourceProperties, err = determineResourceTags(r.Context(), reqType, op, bktName, objName, queries.Get(QueryVersionID), resolver, tagging)
resourceProperties, err = determineResourceTags(ctx, reqType, op, bktName, objName, queries.Get(QueryVersionID), resolver, tagging)
if err != nil {
return nil, nil, fmt.Errorf("determine resource tags: %w", err)
}

View file

@ -166,7 +166,7 @@ func Request(log *zap.Logger, settings RequestSettings) Func {
// generate random UUIDv4
id, err := uuid.NewRandom()
if err != nil {
log.Error(logs.FailedToGenerateRequestID, zap.Error(err))
log.Error(logs.FailedToGenerateRequestID, zap.Error(err), logs.TagField(logs.TagDatapath))
}
// set request id into response header
@ -198,7 +198,8 @@ func Request(log *zap.Logger, settings RequestSettings) Func {
r = r.WithContext(SetReqLogger(ctx, reqLogger))
reqLogger.Info(logs.RequestStart, zap.String("host", r.Host),
zap.String("remote_host", reqInfo.RemoteHost), zap.String("namespace", reqInfo.Namespace))
zap.String("remote_host", reqInfo.RemoteHost), zap.String("namespace", reqInfo.Namespace),
logs.TagField(logs.TagDatapath))
// continue execution
h.ServeHTTP(lw, r)

View file

@ -144,6 +144,17 @@ func WriteErrorResponse(w http.ResponseWriter, reqInfo *ReqInfo, err error) (int
return code, nil
}
// WriteErrorResponseNoHeader writes XML encoded error to the response body.
func WriteErrorResponseNoHeader(w http.ResponseWriter, reqInfo *ReqInfo, err error) error {
errorResponse := getAPIErrorResponse(reqInfo, err)
encodedErrorResponse, err := EncodeResponseNoHeader(errorResponse)
if err != nil {
return err
}
return WriteResponseBody(w, encodedErrorResponse)
}
// Write http common headers.
func setCommonHeaders(w http.ResponseWriter) {
w.Header().Set(hdrServerInfo, version.Server)
@ -200,6 +211,18 @@ func EncodeResponse(response interface{}) ([]byte, error) {
return bytesBuffer.Bytes(), nil
}
// EncodeResponseNoHeader encodes response without setting xml.Header.
// Should be used with periodicXMLWriter which sends xml.Header to the client
// with whitespaces to keep connection alive.
func EncodeResponseNoHeader(response interface{}) ([]byte, error) {
var bytesBuffer bytes.Buffer
if err := xml.NewEncoder(&bytesBuffer).Encode(response); err != nil {
return nil, err
}
return bytesBuffer.Bytes(), nil
}
// EncodeToResponse encodes the response into ResponseWriter.
func EncodeToResponse(w http.ResponseWriter, response interface{}) error {
w.WriteHeader(http.StatusOK)
@ -331,7 +354,7 @@ func LogSuccessResponse(l *zap.Logger) Func {
fields = append(fields, zap.String("user", reqInfo.User))
}
reqLogger.Info(logs.RequestEnd, fields...)
reqLogger.Info(logs.RequestEnd, append(fields, logs.TagField(logs.TagDatapath))...)
})
}
}

View file

@ -245,13 +245,14 @@ func errorResponseHandler(w http.ResponseWriter, r *http.Request) {
zap.String("method", reqInfo.API),
zap.String("http method", r.Method),
zap.String("url", r.RequestURI),
logs.TagField(logs.TagDatapath),
}
if wrErr != nil {
fields = append(fields, zap.NamedError("write_response_error", wrErr))
}
log.Error(logs.RequestUnmatched, fields...)
log.Error(logs.RequestUnmatched, append(fields, logs.TagField(logs.TagDatapath))...)
}
}
@ -266,13 +267,14 @@ func notSupportedHandler() http.HandlerFunc {
fields := []zap.Field{
zap.String("http method", r.Method),
zap.String("url", r.RequestURI),
logs.TagField(logs.TagDatapath),
}
if wrErr != nil {
fields = append(fields, zap.NamedError("write_response_error", wrErr))
}
log.Error(logs.NotSupported, fields...)
log.Error(logs.NotSupported, append(fields, logs.TagField(logs.TagDatapath))...)
}
}
}

View file

@ -45,7 +45,7 @@ type centerMock struct {
key *keys.PrivateKey
}
func (c *centerMock) Authenticate(*http.Request) (*middleware.Box, error) {
func (c *centerMock) Authenticate(context.Context, *http.Request) (*middleware.Box, error) {
if c.noAuthHeader {
return nil, middleware.ErrNoAuthorizationHeader
}

View file

@ -20,6 +20,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
grpctracing "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing/grpc"
qostagging "git.frostfs.info/TrueCloudLab/frostfs-qos/tagging"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
@ -53,6 +54,7 @@ import (
"github.com/panjf2000/ants/v2"
"github.com/spf13/viper"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"golang.org/x/exp/slices"
"golang.org/x/text/encoding/ianaindex"
"google.golang.org/grpc"
@ -65,7 +67,7 @@ type (
App struct {
ctr s3middleware.Center
log *zap.Logger
cfg *viper.Viper
cfg *appCfg
pool *pool.Pool
treePool *treepool.Pool
key *keys.PrivateKey
@ -91,6 +93,10 @@ type (
wrkDone chan struct{}
}
tagsConfig struct {
tagLogs sync.Map
}
loggerSettings struct {
mu sync.RWMutex
appMetrics *metrics.AppMetrics
@ -99,6 +105,7 @@ type (
appSettings struct {
logLevel zap.AtomicLevel
httpLogging s3middleware.LogHTTPConfig
tagsConfig *tagsConfig
maxClient maxClientsConfig
defaultMaxAge int
reconnectInterval time.Duration
@ -132,19 +139,61 @@ type (
tombstoneMembersSize int
tombstoneLifetime uint64
tlsTerminationHeader string
listingKeepaliveThrottle time.Duration
}
maxClientsConfig struct {
deadline time.Duration
count int
}
Logger struct {
logger *zap.Logger
lvl zap.AtomicLevel
}
)
func (t *tagsConfig) LevelEnabled(tag string, tgtLevel zapcore.Level) bool {
lvl, ok := t.tagLogs.Load(tag)
if !ok {
return false
}
return lvl.(zapcore.Level).Enabled(tgtLevel)
}
func (t *tagsConfig) update(cfg *viper.Viper) error {
tags, err := fetchLogTagsConfig(cfg)
if err != nil {
return err
}
t.tagLogs.Range(func(key, value any) bool {
k := key.(string)
v := value.(zapcore.Level)
if lvl, ok := tags[k]; ok {
if lvl != v {
t.tagLogs.Store(key, lvl)
}
} else {
t.tagLogs.Delete(key)
delete(tags, k)
}
return true
})
for k, v := range tags {
t.tagLogs.Store(k, v)
}
return nil
}
func newTagsConfig(v *viper.Viper) *tagsConfig {
var t tagsConfig
if err := t.update(v); err != nil {
// panic here is analogue of the similar panic during common log level initialization.
panic(err.Error())
}
return &t
}
func (s *loggerSettings) DroppedLogsInc() {
s.mu.RLock()
defer s.mu.RUnlock()
@ -161,15 +210,17 @@ func (s *loggerSettings) setMetrics(appMetrics *metrics.AppMetrics) {
s.appMetrics = appMetrics
}
func newApp(ctx context.Context, v *viper.Viper) *App {
func newApp(ctx context.Context, cfg *appCfg) *App {
logSettings := &loggerSettings{}
log := pickLogger(v, logSettings)
settings := newAppSettings(log, v)
appCache := layer.NewCache(getCacheOptions(v, log.logger))
tagConfig := newTagsConfig(cfg.config())
log := pickLogger(cfg.config(), logSettings, tagConfig)
settings := newAppSettings(log, cfg.config())
settings.tagsConfig = tagConfig
appCache := layer.NewCache(getCacheOptions(cfg.config(), log.logger))
app := &App{
log: log.logger,
cfg: v,
cfg: cfg,
cache: appCache,
webDone: make(chan struct{}, 1),
@ -184,6 +235,10 @@ func newApp(ctx context.Context, v *viper.Viper) *App {
return app
}
func (a *App) config() *viper.Viper {
return a.cfg.config()
}
func (a *App) init(ctx context.Context) {
a.initPools(ctx)
a.initResolver()
@ -198,10 +253,10 @@ func (a *App) init(ctx context.Context) {
}
func (a *App) initAuthCenter(ctx context.Context) {
if a.cfg.IsSet(cfgContainersAccessBox) {
if a.config().IsSet(cfgContainersAccessBox) {
cnrID, err := a.resolveContainerID(ctx, cfgContainersAccessBox)
if err != nil {
a.log.Fatal(logs.CouldNotFetchAccessBoxContainerInfo, zap.Error(err))
a.log.Fatal(logs.CouldNotFetchAccessBoxContainerInfo, zap.Error(err), logs.TagField(logs.TagApp))
}
a.settings.accessbox = &cnrID
}
@ -209,36 +264,33 @@ func (a *App) initAuthCenter(ctx context.Context) {
cfg := tokens.Config{
FrostFS: frostfs.NewAuthmateFrostFS(frostfs.NewFrostFS(a.pool, a.key), a.log),
Key: a.key,
CacheConfig: getAccessBoxCacheConfig(a.cfg, a.log),
RemovingCheckAfterDurations: fetchRemovingCheckInterval(a.cfg, a.log),
CacheConfig: getAccessBoxCacheConfig(a.config(), a.log),
RemovingCheckAfterDurations: fetchRemovingCheckInterval(a.config(), a.log),
}
a.ctr = auth.New(tokens.New(cfg), a.cfg.GetStringSlice(cfgAllowedAccessKeyIDPrefixes), a.settings)
a.ctr = auth.New(tokens.New(cfg), a.config().GetStringSlice(cfgAllowedAccessKeyIDPrefixes), a.settings)
}
func (a *App) initLayer(ctx context.Context) {
// prepare random key for anonymous requests
randomKey, err := keys.NewPrivateKey()
if err != nil {
a.log.Fatal(logs.CouldntGenerateRandomKey, zap.Error(err))
a.log.Fatal(logs.CouldntGenerateRandomKey, zap.Error(err), logs.TagField(logs.TagApp))
}
var gateOwner user.ID
user.IDFromKey(&gateOwner, a.key.PrivateKey.PublicKey)
var corsCnrInfo *data.BucketInfo
if a.cfg.IsSet(cfgContainersCORS) {
corsCnrInfo, err = a.fetchContainerInfo(ctx, cfgContainersCORS)
if err != nil {
a.log.Fatal(logs.CouldNotFetchCORSContainerInfo, zap.Error(err))
}
corsCnrInfo, err := a.fetchContainerInfo(ctx, cfgContainersCORS)
if err != nil {
a.log.Fatal(logs.CouldNotFetchCORSContainerInfo, zap.Error(err), logs.TagField(logs.TagApp))
}
var lifecycleCnrInfo *data.BucketInfo
if a.cfg.IsSet(cfgContainersLifecycle) {
if a.config().IsSet(cfgContainersLifecycle) {
lifecycleCnrInfo, err = a.fetchContainerInfo(ctx, cfgContainersLifecycle)
if err != nil {
a.log.Fatal(logs.CouldNotFetchLifecycleContainerInfo, zap.Error(err))
a.log.Fatal(logs.CouldNotFetchLifecycleContainerInfo, zap.Error(err), logs.TagField(logs.TagApp))
}
}
@ -264,7 +316,7 @@ func (a *App) initLayer(ctx context.Context) {
func (a *App) initWorkerPool() *ants.Pool {
workerPool, err := ants.NewPool(a.settings.workerPoolSize)
if err != nil {
a.log.Fatal(logs.FailedToCreateWorkerPool, zap.Error(err))
a.log.Fatal(logs.FailedToCreateWorkerPool, zap.Error(err), logs.TagField(logs.TagApp))
}
return workerPool
}
@ -273,6 +325,7 @@ func newAppSettings(log *Logger, v *viper.Viper) *appSettings {
settings := &appSettings{
logLevel: log.lvl,
httpLogging: s3middleware.LogHTTPConfig{},
tagsConfig: newTagsConfig(v),
maxClient: newMaxClients(v),
defaultMaxAge: fetchDefaultMaxAge(v, log.logger),
reconnectInterval: fetchReconnectInterval(v),
@ -319,6 +372,7 @@ func (s *appSettings) update(v *viper.Viper, log *zap.Logger) {
tombstoneMembersSize := fetchTombstoneMembersSize(v)
tombstoneLifetime := fetchTombstoneLifetime(v)
tlsTerminationHeader := v.GetString(cfgEncryptionTLSTerminationHeader)
listingKeepaliveThrottle := v.GetDuration(cfgKludgeListingKeepAliveThrottle)
s.mu.Lock()
defer s.mu.Unlock()
@ -352,6 +406,7 @@ func (s *appSettings) update(v *viper.Viper, log *zap.Logger) {
s.tombstoneMembersSize = tombstoneMembersSize
s.tombstoneLifetime = tombstoneLifetime
s.tlsTerminationHeader = tlsTerminationHeader
s.listingKeepaliveThrottle = listingKeepaliveThrottle
}
func (s *appSettings) prepareVHSNamespaces(v *viper.Viper, log *zap.Logger, defaultNamespaces []string) map[string]bool {
@ -589,6 +644,12 @@ func (s *appSettings) TombstoneLifetime() uint64 {
return s.tombstoneLifetime
}
func (s *appSettings) ListingKeepaliveThrottle() time.Duration {
s.mu.RLock()
defer s.mu.RUnlock()
return s.listingKeepaliveThrottle
}
func (a *App) initAPI(ctx context.Context) {
a.initLayer(ctx)
a.initHandler()
@ -599,7 +660,7 @@ func (a *App) initMetrics() {
Logger: a.log,
PoolStatistics: frostfs.NewPoolStatistic(a.pool),
TreeStatistic: a.treePool,
Enabled: a.cfg.GetBool(cfgPrometheusEnabled),
Enabled: a.config().GetBool(cfgPrometheusEnabled),
}
a.metrics = metrics.NewAppMetrics(cfg)
@ -609,9 +670,9 @@ func (a *App) initMetrics() {
func (a *App) initFrostfsID(ctx context.Context) {
cli, err := ffidcontract.New(ctx, ffidcontract.Config{
RPCAddress: a.cfg.GetString(cfgRPCEndpoint),
Contract: a.cfg.GetString(cfgFrostfsIDContract),
ProxyContract: a.cfg.GetString(cfgProxyContract),
RPCAddress: a.config().GetString(cfgRPCEndpoint),
Contract: a.config().GetString(cfgFrostfsIDContract),
ProxyContract: a.config().GetString(cfgProxyContract),
Key: a.key,
Waiter: commonclient.WaiterOptions{
IgnoreAlreadyExistsError: false,
@ -619,24 +680,24 @@ func (a *App) initFrostfsID(ctx context.Context) {
},
})
if err != nil {
a.log.Fatal(logs.InitFrostfsIDContractFailed, zap.Error(err))
a.log.Fatal(logs.InitFrostfsIDFailed, zap.Error(err), logs.TagField(logs.TagApp))
}
a.frostfsid, err = frostfsid.NewFrostFSID(frostfsid.Config{
Cache: cache.NewFrostfsIDCache(getFrostfsIDCacheConfig(a.cfg, a.log)),
Cache: cache.NewFrostfsIDCache(getFrostfsIDCacheConfig(a.config(), a.log)),
FrostFSID: cli,
Logger: a.log,
})
if err != nil {
a.log.Fatal(logs.InitFrostfsIDContractFailed, zap.Error(err))
a.log.Fatal(logs.InitFrostfsIDFailed, zap.Error(err), logs.TagField(logs.TagApp))
}
}
func (a *App) initPolicyStorage(ctx context.Context) {
policyContract, err := contract.New(ctx, contract.Config{
RPCAddress: a.cfg.GetString(cfgRPCEndpoint),
Contract: a.cfg.GetString(cfgPolicyContract),
ProxyContract: a.cfg.GetString(cfgProxyContract),
RPCAddress: a.config().GetString(cfgRPCEndpoint),
Contract: a.config().GetString(cfgPolicyContract),
ProxyContract: a.config().GetString(cfgProxyContract),
Key: a.key,
Waiter: commonclient.WaiterOptions{
IgnoreAlreadyExistsError: false,
@ -644,12 +705,12 @@ func (a *App) initPolicyStorage(ctx context.Context) {
},
})
if err != nil {
a.log.Fatal(logs.InitPolicyContractFailed, zap.Error(err))
a.log.Fatal(logs.InitPolicyContractFailed, zap.Error(err), logs.TagField(logs.TagApp))
}
a.policyStorage = policy.NewStorage(policy.StorageConfig{
Contract: policyContract,
Cache: cache.NewMorphPolicyCache(getMorphPolicyCacheConfig(a.cfg, a.log)),
Cache: cache.NewMorphPolicyCache(getMorphPolicyCacheConfig(a.config(), a.log)),
Log: a.log,
})
}
@ -658,26 +719,26 @@ func (a *App) initResolver() {
var err error
a.bucketResolver, err = resolver.NewBucketResolver(a.getResolverOrder(), a.getResolverConfig())
if err != nil {
a.log.Fatal(logs.FailedToCreateResolver, zap.Error(err))
a.log.Fatal(logs.FailedToCreateResolver, zap.Error(err), logs.TagField(logs.TagApp))
}
}
func (a *App) getResolverConfig() *resolver.Config {
return &resolver.Config{
FrostFS: frostfs.NewResolverFrostFS(a.pool),
RPCAddress: a.cfg.GetString(cfgRPCEndpoint),
RPCAddress: a.config().GetString(cfgRPCEndpoint),
}
}
func (a *App) getResolverOrder() []string {
order := a.cfg.GetStringSlice(cfgResolveOrder)
if a.cfg.GetString(cfgRPCEndpoint) == "" {
order := a.config().GetStringSlice(cfgResolveOrder)
if a.config().GetString(cfgRPCEndpoint) == "" {
order = remove(order, resolver.NNSResolver)
a.log.Warn(logs.ResolverNNSWontBeUsedSinceRPCEndpointIsntProvided)
a.log.Warn(logs.ResolverNNSWontBeUsedSinceRPCEndpointIsntProvided, logs.TagField(logs.TagApp))
}
if len(order) == 0 {
a.log.Info(logs.ContainerResolverWillBeDisabled)
a.log.Info(logs.ContainerResolverWillBeDisabled, logs.TagField(logs.TagApp))
}
return order
@ -689,42 +750,42 @@ func (a *App) initTracing(ctx context.Context) {
instanceID = a.servers[0].Address()
}
cfg := tracing.Config{
Enabled: a.cfg.GetBool(cfgTracingEnabled),
Exporter: tracing.Exporter(a.cfg.GetString(cfgTracingExporter)),
Endpoint: a.cfg.GetString(cfgTracingEndpoint),
Enabled: a.config().GetBool(cfgTracingEnabled),
Exporter: tracing.Exporter(a.config().GetString(cfgTracingExporter)),
Endpoint: a.config().GetString(cfgTracingEndpoint),
Service: "frostfs-s3-gw",
InstanceID: instanceID,
Version: version.Version,
}
if trustedCa := a.cfg.GetString(cfgTracingTrustedCa); trustedCa != "" {
if trustedCa := a.config().GetString(cfgTracingTrustedCa); trustedCa != "" {
caBytes, err := os.ReadFile(trustedCa)
if err != nil {
a.log.Warn(logs.FailedToInitializeTracing, zap.Error(err))
a.log.Warn(logs.FailedToInitializeTracing, zap.Error(err), logs.TagField(logs.TagApp))
return
}
certPool := x509.NewCertPool()
ok := certPool.AppendCertsFromPEM(caBytes)
if !ok {
a.log.Warn(logs.FailedToInitializeTracing, zap.String("error", "can't fill cert pool by ca cert"))
a.log.Warn(logs.FailedToInitializeTracing, zap.String("error", "can't fill cert pool by ca cert"), logs.TagField(logs.TagApp))
return
}
cfg.ServerCaCertPool = certPool
}
attributes, err := fetchTracingAttributes(a.cfg)
attributes, err := fetchTracingAttributes(a.config())
if err != nil {
a.log.Warn(logs.FailedToInitializeTracing, zap.Error(err))
a.log.Warn(logs.FailedToInitializeTracing, zap.Error(err), logs.TagField(logs.TagApp))
return
}
cfg.Attributes = attributes
updated, err := tracing.Setup(ctx, cfg)
if err != nil {
a.log.Warn(logs.FailedToInitializeTracing, zap.Error(err))
a.log.Warn(logs.FailedToInitializeTracing, zap.Error(err), logs.TagField(logs.TagApp))
}
if updated {
a.log.Info(logs.TracingConfigUpdated)
a.log.Info(logs.TracingConfigUpdated, logs.TagField(logs.TagApp))
}
}
@ -734,7 +795,7 @@ func (a *App) shutdownTracing() {
defer cancel()
if err := tracing.Shutdown(shdnCtx); err != nil {
a.log.Warn(logs.FailedToShutdownTracing, zap.Error(err))
a.log.Warn(logs.FailedToShutdownTracing, zap.Error(err), logs.TagField(logs.TagApp))
}
}
@ -751,7 +812,7 @@ func newMaxClients(cfg *viper.Viper) maxClientsConfig {
func getDialerSource(logger *zap.Logger, cfg *viper.Viper) *internalnet.DialerSource {
source, err := internalnet.NewDialerSource(fetchMultinetConfig(cfg, logger))
if err != nil {
logger.Fatal(logs.FailedToLoadMultinetConfig, zap.Error(err))
logger.Fatal(logs.FailedToLoadMultinetConfig, zap.Error(err), logs.TagField(logs.TagApp))
}
return source
}
@ -760,75 +821,75 @@ func (a *App) initPools(ctx context.Context) {
var prm pool.InitParameters
var prmTree treepool.InitParameters
password := wallet.GetPassword(a.cfg, cfgWalletPassphrase)
key, err := wallet.GetKeyFromPath(a.cfg.GetString(cfgWalletPath), a.cfg.GetString(cfgWalletAddress), password)
password := wallet.GetPassword(a.config(), cfgWalletPassphrase)
key, err := wallet.GetKeyFromPath(a.config().GetString(cfgWalletPath), a.config().GetString(cfgWalletAddress), password)
if err != nil {
a.log.Fatal(logs.CouldNotLoadFrostFSPrivateKey, zap.Error(err))
a.log.Fatal(logs.CouldNotLoadFrostFSPrivateKey, zap.Error(err), logs.TagField(logs.TagApp))
}
prm.SetKey(&key.PrivateKey)
prmTree.SetKey(key)
a.log.Info(logs.UsingCredentials, zap.String("FrostFS", hex.EncodeToString(key.PublicKey().Bytes())))
a.log.Info(logs.UsingCredentials, zap.String("FrostFS", hex.EncodeToString(key.PublicKey().Bytes())), logs.TagField(logs.TagApp))
for _, peer := range fetchPeers(a.log, a.cfg) {
for _, peer := range fetchPeers(a.log, a.config()) {
prm.AddNode(peer)
prmTree.AddNode(peer)
}
connTimeout := fetchConnectTimeout(a.cfg)
connTimeout := fetchConnectTimeout(a.config())
prm.SetNodeDialTimeout(connTimeout)
prmTree.SetNodeDialTimeout(connTimeout)
prm.SetNodeStreamTimeout(fetchStreamTimeout(a.cfg, cfgStreamTimeout))
prmTree.SetNodeStreamTimeout(fetchStreamTimeout(a.cfg, cfgTreeStreamTimeout))
prm.SetNodeStreamTimeout(fetchStreamTimeout(a.config(), cfgStreamTimeout))
prmTree.SetNodeStreamTimeout(fetchStreamTimeout(a.config(), cfgTreeStreamTimeout))
healthCheckTimeout := fetchHealthCheckTimeout(a.cfg)
healthCheckTimeout := fetchHealthCheckTimeout(a.config())
prm.SetHealthcheckTimeout(healthCheckTimeout)
prmTree.SetHealthcheckTimeout(healthCheckTimeout)
rebalanceInterval := fetchRebalanceInterval(a.cfg)
rebalanceInterval := fetchRebalanceInterval(a.config())
prm.SetClientRebalanceInterval(rebalanceInterval)
prmTree.SetClientRebalanceInterval(rebalanceInterval)
errorThreshold := fetchErrorThreshold(a.cfg)
errorThreshold := fetchErrorThreshold(a.config())
prm.SetErrorThreshold(errorThreshold)
prm.SetGracefulCloseOnSwitchTimeout(fetchSetGracefulCloseOnSwitchTimeout(a.cfg))
prm.SetGracefulCloseOnSwitchTimeout(fetchSetGracefulCloseOnSwitchTimeout(a.config()))
prm.SetLogger(a.log)
prmTree.SetLogger(a.log)
prm.SetLogger(a.log.With(logs.TagField(logs.TagDatapath)))
prmTree.SetLogger(a.log.With(logs.TagField(logs.TagDatapath)))
prmTree.SetMaxRequestAttempts(a.cfg.GetInt(cfgTreePoolMaxAttempts))
prmTree.SetCircuitBreakerThreshold(a.cfg.GetInt(cfgPoolCbThreshold))
prmTree.SetCircuitBreakerDuration(a.cfg.GetDuration(cfgPoolCbBreakDuration))
prmTree.SetMaxRequestAttempts(a.config().GetInt(cfgTreePoolMaxAttempts))
interceptors := []grpc.DialOption{
grpc.WithUnaryInterceptor(grpctracing.NewUnaryClientInteceptor()),
grpc.WithStreamInterceptor(grpctracing.NewStreamClientInterceptor()),
grpc.WithContextDialer(a.settings.dialerSource.GrpcContextDialer()),
grpc.WithChainUnaryInterceptor(qostagging.NewUnaryClientInteceptor()),
grpc.WithChainStreamInterceptor(qostagging.NewStreamClientInterceptor()),
}
prm.SetGRPCDialOptions(interceptors...)
prmTree.SetGRPCDialOptions(interceptors...)
p, err := pool.NewPool(prm)
if err != nil {
a.log.Fatal(logs.FailedToCreateConnectionPool, zap.Error(err))
a.log.Fatal(logs.FailedToCreateConnectionPool, zap.Error(err), logs.TagField(logs.TagApp))
}
if err = p.Dial(ctx); err != nil {
a.log.Fatal(logs.FailedToDialConnectionPool, zap.Error(err))
a.log.Fatal(logs.FailedToDialConnectionPool, zap.Error(err), logs.TagField(logs.TagApp))
}
if a.cfg.GetBool(cfgTreePoolNetmapSupport) {
if a.config().GetBool(cfgTreePoolNetmapSupport) {
prmTree.SetNetMapInfoSource(frostfs.NewSource(frostfs.NewFrostFS(p, key), a.cache))
}
treePool, err := treepool.NewPool(prmTree)
if err != nil {
a.log.Fatal(logs.FailedToCreateTreePool, zap.Error(err))
a.log.Fatal(logs.FailedToCreateTreePool, zap.Error(err), logs.TagField(logs.TagApp))
}
if err = treePool.Dial(ctx); err != nil {
a.log.Fatal(logs.FailedToDialTreePool, zap.Error(err))
a.log.Fatal(logs.FailedToDialTreePool, zap.Error(err), logs.TagField(logs.TagApp))
}
a.treePool = treePool
@ -854,6 +915,7 @@ func (a *App) Wait() {
a.log.Info(logs.ApplicationStarted,
zap.String("name", "frostfs-s3-gw"),
zap.String("version", version.Version),
logs.TagField(logs.TagApp),
)
a.metrics.State().SetVersion(version.Version)
@ -861,7 +923,7 @@ func (a *App) Wait() {
<-a.webDone // wait for web-server to be stopped
a.log.Info(logs.ApplicationFinished)
a.log.Info(logs.ApplicationFinished, logs.TagField(logs.TagApp))
}
func (a *App) setHealthStatus() {
@ -896,10 +958,10 @@ func (a *App) Serve(ctx context.Context) {
srv := new(http.Server)
srv.Handler = chiRouter
srv.ErrorLog = zap.NewStdLog(a.log)
srv.ReadTimeout = a.cfg.GetDuration(cfgWebReadTimeout)
srv.ReadHeaderTimeout = a.cfg.GetDuration(cfgWebReadHeaderTimeout)
srv.WriteTimeout = a.cfg.GetDuration(cfgWebWriteTimeout)
srv.IdleTimeout = a.cfg.GetDuration(cfgWebIdleTimeout)
srv.ReadTimeout = a.config().GetDuration(cfgWebReadTimeout)
srv.ReadHeaderTimeout = a.config().GetDuration(cfgWebReadHeaderTimeout)
srv.WriteTimeout = a.config().GetDuration(cfgWebWriteTimeout)
srv.IdleTimeout = a.config().GetDuration(cfgWebIdleTimeout)
a.startServices()
@ -907,11 +969,11 @@ func (a *App) Serve(ctx context.Context) {
for i := range servs {
go func(i int) {
a.log.Info(logs.StartingServer, zap.String("address", servs[i].Address()))
a.log.Info(logs.StartingServer, zap.String("address", servs[i].Address()), logs.TagField(logs.TagApp))
if err := srv.Serve(servs[i].Listener()); err != nil && err != http.ErrServerClosed {
a.metrics.MarkUnhealthy(servs[i].Address())
a.log.Fatal(logs.ListenAndServe, zap.Error(err))
a.log.Fatal(logs.ListenAndServe, zap.Error(err), logs.TagField(logs.TagApp))
}
}(i)
}
@ -936,7 +998,7 @@ LOOP:
ctx, cancel := shutdownContext()
defer cancel()
a.log.Info(logs.StoppingServer, zap.Error(srv.Shutdown(ctx)))
a.log.Info(logs.StoppingServer, zap.Error(srv.Shutdown(ctx)), logs.TagField(logs.TagApp))
a.metrics.Shutdown()
a.stopServices()
@ -950,23 +1012,23 @@ func shutdownContext() (context.Context, context.CancelFunc) {
}
func (a *App) configReload(ctx context.Context) {
a.log.Info(logs.SIGHUPConfigReloadStarted)
a.log.Info(logs.SIGHUPConfigReloadStarted, logs.TagField(logs.TagApp))
if !a.cfg.IsSet(cmdConfig) && !a.cfg.IsSet(cmdConfigDir) {
a.log.Warn(logs.FailedToReloadConfigBecauseItsMissed)
if !a.config().IsSet(cmdConfig) && !a.config().IsSet(cmdConfigDir) {
a.log.Warn(logs.FailedToReloadConfigBecauseItsMissed, logs.TagField(logs.TagApp))
return
}
if err := readInConfig(a.cfg); err != nil {
a.log.Warn(logs.FailedToReloadConfig, zap.Error(err))
if err := a.cfg.reload(); err != nil {
a.log.Warn(logs.FailedToReloadConfig, zap.Error(err), logs.TagField(logs.TagApp))
return
}
if err := a.bucketResolver.UpdateResolvers(a.getResolverOrder()); err != nil {
a.log.Warn(logs.FailedToReloadResolvers, zap.Error(err))
a.log.Warn(logs.FailedToReloadResolvers, zap.Error(err), logs.TagField(logs.TagApp))
}
if err := a.updateServers(); err != nil {
a.log.Warn(logs.FailedToReloadServerParameters, zap.Error(err))
a.log.Warn(logs.FailedToReloadServerParameters, zap.Error(err), logs.TagField(logs.TagApp))
}
a.setRuntimeParameters()
@ -976,41 +1038,45 @@ func (a *App) configReload(ctx context.Context) {
a.updateSettings()
a.metrics.SetEnabled(a.cfg.GetBool(cfgPrometheusEnabled))
a.metrics.SetEnabled(a.config().GetBool(cfgPrometheusEnabled))
a.initTracing(ctx)
a.setHealthStatus()
a.log.Info(logs.SIGHUPConfigReloadCompleted)
a.log.Info(logs.SIGHUPConfigReloadCompleted, logs.TagField(logs.TagApp))
}
func (a *App) updateSettings() {
if lvl, err := getLogLevel(a.cfg); err != nil {
a.log.Warn(logs.LogLevelWontBeUpdated, zap.Error(err))
if lvl, err := getLogLevel(a.config()); err != nil {
a.log.Warn(logs.LogLevelWontBeUpdated, zap.Error(err), logs.TagField(logs.TagApp))
} else {
a.settings.logLevel.SetLevel(lvl)
}
if err := a.settings.dialerSource.Update(fetchMultinetConfig(a.cfg, a.log)); err != nil {
a.log.Warn(logs.MultinetConfigWontBeUpdated, zap.Error(err))
if err := a.settings.dialerSource.Update(fetchMultinetConfig(a.config(), a.log)); err != nil {
a.log.Warn(logs.MultinetConfigWontBeUpdated, zap.Error(err), logs.TagField(logs.TagApp))
}
a.settings.update(a.cfg, a.log)
if err := a.settings.tagsConfig.update(a.config()); err != nil {
a.log.Warn(logs.TagsLogConfigWontBeUpdated, zap.Error(err), logs.TagField(logs.TagApp))
}
a.settings.update(a.config(), a.log)
}
func (a *App) startServices() {
a.services = a.services[:0]
pprofService := NewPprofService(a.cfg, a.log)
pprofService := NewPprofService(a.config(), a.log)
a.services = append(a.services, pprofService)
go pprofService.Start()
prometheusService := NewPrometheusService(a.cfg, a.log, a.metrics.Handler())
prometheusService := NewPrometheusService(a.config(), a.log, a.metrics.Handler())
a.services = append(a.services, prometheusService)
go prometheusService.Start()
}
func (a *App) initServers(ctx context.Context) {
serversInfo := fetchServers(a.cfg, a.log)
serversInfo := fetchServers(a.config(), a.log)
a.servers = make([]Server, 0, len(serversInfo))
for _, serverInfo := range serversInfo {
@ -1022,22 +1088,22 @@ func (a *App) initServers(ctx context.Context) {
if err != nil {
a.unbindServers = append(a.unbindServers, serverInfo)
a.metrics.MarkUnhealthy(serverInfo.Address)
a.log.Warn(logs.FailedToAddServer, append(fields, zap.Error(err))...)
a.log.Warn(logs.FailedToAddServer, append(fields, zap.Error(err), logs.TagField(logs.TagApp))...)
continue
}
a.metrics.MarkHealthy(serverInfo.Address)
a.servers = append(a.servers, srv)
a.log.Info(logs.AddServer, fields...)
a.log.Info(logs.AddServer, append(fields, logs.TagField(logs.TagApp))...)
}
if len(a.servers) == 0 {
a.log.Fatal(logs.NoHealthyServers)
a.log.Fatal(logs.NoHealthyServers, logs.TagField(logs.TagApp))
}
}
func (a *App) updateServers() error {
serversInfo := fetchServers(a.cfg, a.log)
serversInfo := fetchServers(a.config(), a.log)
a.mu.Lock()
defer a.mu.Unlock()
@ -1050,8 +1116,8 @@ func (a *App) updateServers() error {
if err := ser.UpdateCert(serverInfo.TLS.CertFile, serverInfo.TLS.KeyFile); err != nil {
return fmt.Errorf("failed to update tls certs: %w", err)
}
found = true
}
found = true
} else if unbind := a.updateUnbindServerInfo(serverInfo); unbind {
found = true
}
@ -1136,7 +1202,7 @@ func (a *App) initHandler() {
a.api, err = handler.New(a.log, a.obj, a.settings, a.policyStorage, a.frostfsid)
if err != nil {
a.log.Fatal(logs.CouldNotInitializeAPIHandler, zap.Error(err))
a.log.Fatal(logs.CouldNotInitializeAPIHandler, zap.Error(err), logs.TagField(logs.TagApp))
}
}
@ -1168,16 +1234,18 @@ func (a *App) getServers() []Server {
func (a *App) setRuntimeParameters() {
if len(os.Getenv("GOMEMLIMIT")) != 0 {
// default limit < yaml limit < app env limit < GOMEMLIMIT
a.log.Warn(logs.RuntimeSoftMemoryDefinedWithGOMEMLIMIT)
a.log.Warn(logs.RuntimeSoftMemoryDefinedWithGOMEMLIMIT, logs.TagField(logs.TagApp))
return
}
softMemoryLimit := fetchSoftMemoryLimit(a.cfg)
softMemoryLimit := fetchSoftMemoryLimit(a.config())
previous := debug.SetMemoryLimit(softMemoryLimit)
if softMemoryLimit != previous {
a.log.Info(logs.RuntimeSoftMemoryLimitUpdated,
zap.Int64("new_value", softMemoryLimit),
zap.Int64("old_value", previous))
zap.Int64("old_value", previous),
logs.TagField(logs.TagApp),
)
}
}
@ -1203,7 +1271,7 @@ func (a *App) tryReconnect(ctx context.Context, sr *http.Server) bool {
a.mu.Lock()
defer a.mu.Unlock()
a.log.Info(logs.ServerReconnecting)
a.log.Info(logs.ServerReconnecting, logs.TagField(logs.TagApp))
var failedServers []ServerInfo
for _, serverInfo := range a.unbindServers {
@ -1214,23 +1282,23 @@ func (a *App) tryReconnect(ctx context.Context, sr *http.Server) bool {
srv, err := newServer(ctx, serverInfo)
if err != nil {
a.log.Warn(logs.ServerReconnectFailed, zap.Error(err))
a.log.Warn(logs.ServerReconnectFailed, zap.Error(err), logs.TagField(logs.TagApp))
failedServers = append(failedServers, serverInfo)
a.metrics.MarkUnhealthy(serverInfo.Address)
continue
}
go func() {
a.log.Info(logs.StartingServer, zap.String("address", srv.Address()))
a.log.Info(logs.StartingServer, zap.String("address", srv.Address()), logs.TagField(logs.TagApp))
a.metrics.MarkHealthy(serverInfo.Address)
if err = sr.Serve(srv.Listener()); err != nil && !errors.Is(err, http.ErrServerClosed) {
a.log.Warn(logs.ListenAndServe, zap.Error(err))
a.log.Warn(logs.ListenAndServe, zap.Error(err), logs.TagField(logs.TagApp))
a.metrics.MarkUnhealthy(serverInfo.Address)
}
}()
a.servers = append(a.servers, srv)
a.log.Info(logs.ServerReconnectedSuccessfully, fields...)
a.log.Info(logs.ServerReconnectedSuccessfully, append(fields, logs.TagField(logs.TagApp))...)
}
a.unbindServers = failedServers
@ -1248,7 +1316,7 @@ func (a *App) fetchContainerInfo(ctx context.Context, cfgKey string) (info *data
}
func (a *App) resolveContainerID(ctx context.Context, cfgKey string) (cid.ID, error) {
containerString := a.cfg.GetString(cfgKey)
containerString := a.config().GetString(cfgKey)
var id cid.ID
if err := id.DecodeString(containerString); err != nil {

View file

@ -10,6 +10,7 @@ import (
"sort"
"strconv"
"strings"
"sync"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
@ -20,20 +21,13 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/version"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
"git.frostfs.info/TrueCloudLab/zapjournald"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/ssgreg/journald"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
const (
destinationStdout = "stdout"
destinationJournald = "journald"
wildcardPlaceholder = "<wildcard>"
)
const wildcardPlaceholder = "<wildcard>"
const (
defaultRebalanceInterval = 60 * time.Second
@ -42,9 +36,6 @@ const (
defaultStreamTimeout = 10 * time.Second
defaultShutdownTimeout = 15 * time.Second
defaultCbThreshold = 10
defaultCbBreakDuration = 10 * time.Second
defaultLoggerSamplerInterval = 1 * time.Second
defaultGracefulCloseOnSwitchTimeout = 10 * time.Second
@ -91,7 +82,8 @@ var (
defaultDefaultNamespaces = []string{"", "root"}
)
const ( // Settings.
// Settings.
const (
// Logger.
cfgLoggerLevel = "logger.level"
cfgLoggerDestination = "logger.destination"
@ -101,6 +93,11 @@ const ( // Settings.
cfgLoggerSamplingThereafter = "logger.sampling.thereafter"
cfgLoggerSamplingInterval = "logger.sampling.interval"
cfgLoggerTags = "logger.tags"
cfgLoggerTagsPrefixTmpl = cfgLoggerTags + ".%d."
cfgLoggerTagsNameTmpl = cfgLoggerTagsPrefixTmpl + "name"
cfgLoggerTagsLevelTmpl = cfgLoggerTagsPrefixTmpl + "level"
// HttpLogging.
cfgHTTPLoggingEnabled = "http_logging.enabled"
cfgHTTPLoggingMaxBody = "http_logging.max_body"
@ -123,14 +120,12 @@ const ( // Settings.
cfgTLSCertFile = "tls.cert_file"
// Pool config.
cfgConnectTimeout = "connect_timeout"
cfgStreamTimeout = "stream_timeout"
cfgTreeStreamTimeout = "tree_stream_timeout"
cfgHealthcheckTimeout = "healthcheck_timeout"
cfgRebalanceInterval = "rebalance_interval"
cfgPoolErrorThreshold = "pool_error_threshold"
cfgPoolCbThreshold = "pool_cb_threshold"
cfgPoolCbBreakDuration = "pool_cb_break_duration"
cfgConnectTimeout = "connect_timeout"
cfgStreamTimeout = "stream_timeout"
cfgTreeStreamTimeout = "tree_stream_timeout"
cfgHealthcheckTimeout = "healthcheck_timeout"
cfgRebalanceInterval = "rebalance_interval"
cfgPoolErrorThreshold = "pool_error_threshold"
// Caching.
cfgObjectsCacheLifetime = "cache.objects.lifetime"
@ -207,6 +202,8 @@ const ( // Settings.
cfgKludgeBypassContentEncodingCheckInChunks = "kludge.bypass_content_encoding_check_in_chunks"
cfgKludgeDefaultNamespaces = "kludge.default_namespaces"
cfgKludgeProfile = "kludge.profile"
cfgKludgeListingKeepAliveThrottle = "kludge.listing_keepalive_throttle"
// Web.
cfgWebReadTimeout = "web.read_timeout"
cfgWebReadHeaderTimeout = "web.read_header_timeout"
@ -307,6 +304,49 @@ var ignore = map[string]struct{}{
cmdVersion: {},
}
type appCfg struct {
flags *pflag.FlagSet
mu sync.RWMutex
settings *viper.Viper
}
func (a *appCfg) reload() error {
old := a.config()
v, err := newViper(a.flags)
if err != nil {
return err
}
if old.IsSet(cmdConfig) {
v.Set(cmdConfig, old.Get(cmdConfig))
}
if old.IsSet(cmdConfigDir) {
v.Set(cmdConfigDir, old.Get(cmdConfigDir))
}
if err = readInConfig(v); err != nil {
return err
}
a.setConfig(v)
return nil
}
func (a *appCfg) config() *viper.Viper {
a.mu.RLock()
defer a.mu.RUnlock()
return a.settings
}
func (a *appCfg) setConfig(v *viper.Viper) {
a.mu.Lock()
a.settings = v
a.mu.Unlock()
}
func fetchConnectTimeout(cfg *viper.Viper) time.Duration {
connTimeout := cfg.GetDuration(cfgConnectTimeout)
if connTimeout <= 0 {
@ -431,14 +471,18 @@ func fetchDefaultPolicy(l *zap.Logger, cfg *viper.Viper) netmap.PlacementPolicy
policyStr := cfg.GetString(cfgPolicyDefault)
if err := policy.DecodeString(policyStr); err != nil {
l.Warn(logs.FailedToParseDefaultLocationConstraint,
zap.String("policy", policyStr), zap.String("default", defaultPlacementPolicy), zap.Error(err))
zap.String("policy", policyStr), zap.String("default", defaultPlacementPolicy),
zap.Error(err), logs.TagField(logs.TagApp))
} else {
return policy
}
}
if err := policy.DecodeString(defaultPlacementPolicy); err != nil {
l.Fatal(logs.FailedToParseDefaultDefaultLocationConstraint, zap.String("policy", defaultPlacementPolicy))
l.Fatal(logs.FailedToParseDefaultDefaultLocationConstraint,
zap.String("policy", defaultPlacementPolicy),
logs.TagField(logs.TagApp),
)
}
return policy
@ -451,7 +495,9 @@ func fetchCacheLifetime(v *viper.Viper, l *zap.Logger, cfgEntry string, defaultV
l.Error(logs.InvalidLifetimeUsingDefaultValue,
zap.String("parameter", cfgEntry),
zap.Duration("value in config", lifetime),
zap.Duration("default", defaultValue))
zap.Duration("default", defaultValue),
logs.TagField(logs.TagApp),
)
} else {
return lifetime
}
@ -467,7 +513,9 @@ func fetchCacheSize(v *viper.Viper, l *zap.Logger, cfgEntry string, defaultValue
l.Error(logs.InvalidCacheSizeUsingDefaultValue,
zap.String("parameter", cfgEntry),
zap.Int("value in config", size),
zap.Int("default", defaultValue))
zap.Int("default", defaultValue),
logs.TagField(logs.TagApp),
)
} else {
return size
}
@ -489,7 +537,8 @@ func fetchRemovingCheckInterval(v *viper.Viper, l *zap.Logger) time.Duration {
l.Error(logs.InvalidAccessBoxCacheRemovingCheckInterval,
zap.String("parameter", cfgAccessBoxCacheRemovingCheckInterval),
zap.Duration("value in config", duration),
zap.Duration("default", defaultAccessBoxCacheRemovingCheckInterval))
zap.Duration("default", defaultAccessBoxCacheRemovingCheckInterval),
logs.TagField(logs.TagApp))
return defaultAccessBoxCacheRemovingCheckInterval
}
@ -503,7 +552,9 @@ func fetchDefaultMaxAge(cfg *viper.Viper, l *zap.Logger) int {
if defaultMaxAge <= 0 && defaultMaxAge != -1 {
l.Fatal(logs.InvalidDefaultMaxAge,
zap.String("parameter", cfgDefaultMaxAge),
zap.String("value in config", strconv.Itoa(defaultMaxAge)))
zap.String("value in config", strconv.Itoa(defaultMaxAge)),
logs.TagField(logs.TagApp),
)
}
}
@ -514,14 +565,19 @@ func fetchRegionMappingPolicies(l *zap.Logger, cfg *viper.Viper) map[string]netm
filepath := cfg.GetString(cfgPolicyRegionMapFile)
regionPolicyMap, err := readRegionMap(filepath)
if err != nil {
l.Warn(logs.FailedToReadRegionMapFilePolicies, zap.String("file", filepath), zap.Error(err))
l.Warn(logs.FailedToReadRegionMapFilePolicies,
zap.String("file", filepath),
zap.Error(err),
logs.TagField(logs.TagApp))
return make(map[string]netmap.PlacementPolicy)
}
regionMap := make(map[string]netmap.PlacementPolicy, len(regionPolicyMap))
for region, policy := range regionPolicyMap {
if region == api.DefaultLocationConstraint {
l.Warn(logs.DefaultLocationConstraintCantBeOverriden, zap.String("policy", policy))
l.Warn(logs.DefaultLocationConstraintCantBeOverriden,
zap.String("policy", policy),
logs.TagField(logs.TagApp))
continue
}
@ -536,7 +592,10 @@ func fetchRegionMappingPolicies(l *zap.Logger, cfg *viper.Viper) map[string]netm
continue
}
l.Warn(logs.FailedToParseLocationConstraint, zap.String("region", region), zap.String("policy", policy))
l.Warn(logs.FailedToParseLocationConstraint,
zap.String("region", region),
zap.String("policy", policy),
logs.TagField(logs.TagApp))
}
return regionMap
@ -568,7 +627,11 @@ func fetchDefaultCopiesNumbers(l *zap.Logger, v *viper.Viper) []uint32 {
parsedValue, err := strconv.ParseUint(unparsed[i], 10, 32)
if err != nil {
l.Warn(logs.FailedToParseDefaultCopiesNumbers,
zap.Strings("copies numbers", unparsed), zap.Uint32s("default", defaultCopiesNumbers), zap.Error(err))
zap.Strings("copies numbers", unparsed),
zap.Uint32s("default", defaultCopiesNumbers),
zap.Error(err),
logs.TagField(logs.TagApp),
)
return defaultCopiesNumbers
}
result[i] = uint32(parsedValue)
@ -624,15 +687,17 @@ func fetchCopiesNumbers(l *zap.Logger, v *viper.Viper) map[string][]uint32 {
for j := range vector {
parsedValue, err := strconv.ParseUint(vector[j], 10, 32)
if err != nil {
l.Warn(logs.FailedToParseCopiesNumbers, zap.String("location", constraint),
zap.Strings("copies numbers", vector), zap.Error(err))
l.Warn(logs.FailedToParseCopiesNumbers,
zap.String("location", constraint),
zap.Strings("copies numbers", vector), zap.Error(err),
logs.TagField(logs.TagApp))
continue
}
vector32[j] = uint32(parsedValue)
}
copiesNums[constraint] = vector32
l.Info(logs.ConstraintAdded, zap.String("location", constraint), zap.Strings("copies numbers", vector))
l.Info(logs.ConstraintAdded, zap.String("location", constraint), zap.Strings("copies numbers", vector), logs.TagField(logs.TagApp))
}
return copiesNums
}
@ -641,7 +706,9 @@ func fetchDefaultNamespaces(l *zap.Logger, v *viper.Viper) []string {
defaultNamespaces := v.GetStringSlice(cfgKludgeDefaultNamespaces)
if len(defaultNamespaces) == 0 {
defaultNamespaces = defaultDefaultNamespaces
l.Warn(logs.DefaultNamespacesCannotBeEmpty, zap.Strings("namespaces", defaultNamespaces))
l.Warn(logs.DefaultNamespacesCannotBeEmpty,
zap.Strings("namespaces", defaultNamespaces),
logs.TagField(logs.TagApp))
}
for i := range defaultNamespaces { // to be set namespaces in env variable as `S3_GW_KLUDGE_DEFAULT_NAMESPACES="" 'root'`
@ -665,7 +732,7 @@ func fetchNamespacesConfig(l *zap.Logger, v *viper.Viper) (NamespacesConfig, []s
nsConfig, err := readNamespacesConfig(v.GetString(cfgNamespacesConfig))
if err != nil {
l.Warn(logs.FailedToParseNamespacesConfig, zap.Error(err))
l.Warn(logs.FailedToParseNamespacesConfig, zap.Error(err), logs.TagField(logs.TagApp))
}
defaultNamespacesNames := fetchDefaultNamespaces(l, v)
@ -679,11 +746,13 @@ func fetchNamespacesConfig(l *zap.Logger, v *viper.Viper) (NamespacesConfig, []s
}
if len(overrideDefaults) > 0 {
l.Warn(logs.DefaultNamespaceConfigValuesBeOverwritten)
l.Warn(logs.DefaultNamespaceConfigValuesBeOverwritten, logs.TagField(logs.TagApp))
defaultNSValue.LocationConstraints = overrideDefaults[0].LocationConstraints
defaultNSValue.CopiesNumbers = overrideDefaults[0].CopiesNumbers
if len(overrideDefaults) > 1 {
l.Warn(logs.MultipleDefaultOverridesFound, zap.String("name", overrideDefaults[0].Name))
l.Warn(logs.MultipleDefaultOverridesFound,
zap.String("name", overrideDefaults[0].Name),
logs.TagField(logs.TagApp))
}
}
@ -726,7 +795,7 @@ func fetchPeers(l *zap.Logger, v *viper.Viper) []pool.NodeParam {
priority := v.GetInt(key + "priority")
if address == "" {
l.Warn(logs.SkipEmptyAddress)
l.Warn(logs.SkipEmptyAddress, logs.TagField(logs.TagApp))
break
}
if weight <= 0 { // unspecified or wrong
@ -741,7 +810,9 @@ func fetchPeers(l *zap.Logger, v *viper.Viper) []pool.NodeParam {
l.Info(logs.AddedStoragePeer,
zap.Int("priority", priority),
zap.String("address", address),
zap.Float64("weight", weight))
zap.Float64("weight", weight),
logs.TagField(logs.TagApp),
)
}
return nodes
@ -765,7 +836,7 @@ func fetchServers(v *viper.Viper, log *zap.Logger) []ServerInfo {
}
if _, ok := seen[serverInfo.Address]; ok {
log.Warn(logs.WarnDuplicateAddress, zap.String("address", serverInfo.Address))
log.Warn(logs.WarnDuplicateAddress, zap.String("address", serverInfo.Address), logs.TagField(logs.TagApp))
continue
}
seen[serverInfo.Address] = struct{}{}
@ -794,13 +865,13 @@ func fetchVHSNamespaces(v *viper.Viper, log *zap.Logger) map[string]bool {
nsMap := v.GetStringMap(cfgVHSNamespaces)
for ns, val := range nsMap {
if _, ok := vhsNamespacesEnabled[ns]; ok {
log.Warn(logs.WarnDuplicateNamespaceVHS, zap.String("namespace", ns))
log.Warn(logs.WarnDuplicateNamespaceVHS, zap.String("namespace", ns), logs.TagField(logs.TagApp))
continue
}
enabledFlag, ok := val.(bool)
if !ok {
log.Warn(logs.WarnValueVHSEnabledFlagWrongType, zap.String("namespace", ns))
log.Warn(logs.WarnValueVHSEnabledFlagWrongType, zap.String("namespace", ns), logs.TagField(logs.TagApp))
continue
}
@ -884,7 +955,42 @@ func fetchTombstoneWorkerPoolSize(v *viper.Viper) int {
return tombstoneWorkerPoolSize
}
func newSettings() *viper.Viper {
func fetchLogTagsConfig(v *viper.Viper) (map[string]zapcore.Level, error) {
res := make(map[string]zapcore.Level)
defaultLevel := v.GetString(cfgLoggerLevel)
var defaultLvl zapcore.Level
if err := defaultLvl.Set(defaultLevel); err != nil {
return nil, fmt.Errorf("failed to parse log level, unknown level: '%s'", defaultLevel)
}
for i := 0; ; i++ {
name := v.GetString(fmt.Sprintf(cfgLoggerTagsNameTmpl, i))
if name == "" {
break
}
lvl := defaultLvl
level := v.GetString(fmt.Sprintf(cfgLoggerTagsLevelTmpl, i))
if level != "" {
if err := lvl.Set(level); err != nil {
return nil, fmt.Errorf("failed to parse log tags config, unknown level: '%s'", level)
}
}
res[name] = lvl
}
if len(res) == 0 && !v.IsSet(cfgLoggerTags) {
for _, tag := range defaultTags {
res[tag] = defaultLvl
}
}
return res, nil
}
func newViper(flags *pflag.FlagSet) (*viper.Viper, error) {
v := viper.New()
v.AutomaticEnv()
@ -893,6 +999,20 @@ func newSettings() *viper.Viper {
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.AllowEmptyEnv(true)
if err := bindFlags(v, flags); err != nil {
return nil, err
}
setDefaults(v, flags)
if v.IsSet(cfgServer+".0."+cfgTLSKeyFile) && v.IsSet(cfgServer+".0."+cfgTLSCertFile) {
v.Set(cfgServer+".0."+cfgTLSEnabled, true)
}
return v, nil
}
func newSettings() *appCfg {
// flags setup:
flags := pflag.NewFlagSet("commandline", pflag.ExitOnError)
flags.SetOutput(os.Stdout)
@ -920,15 +1040,71 @@ func newSettings() *viper.Viper {
flags.String(cfgTLSCertFile, "", "TLS certificate file to use")
flags.String(cfgTLSKeyFile, "", "TLS key file to use")
peers := flags.StringArrayP(cfgPeers, "p", nil, "set FrostFS nodes")
flags.StringArrayP(cfgPeers, "p", nil, "set FrostFS nodes")
flags.StringP(cfgRPCEndpoint, "r", "", "set RPC endpoint")
resolveMethods := flags.StringSlice(cfgResolveOrder, []string{resolver.DNSResolver}, "set bucket name resolve order")
flags.StringSlice(cfgResolveOrder, []string{resolver.DNSResolver}, "set bucket name resolve order")
domains := flags.StringSliceP(cfgListenDomains, "d", nil, "set domains to be listened")
flags.StringSliceP(cfgListenDomains, "d", nil, "set domains to be listened")
// set defaults:
if err := flags.Parse(os.Args); err != nil {
panic(err)
}
v, err := newViper(flags)
if err != nil {
panic(fmt.Errorf("bind flags: %w", err))
}
switch {
case help != nil && *help:
fmt.Printf("FrostFS S3 gateway %s\n", version.Version)
flags.PrintDefaults()
fmt.Println()
fmt.Println("Default environments:")
fmt.Println()
keys := v.AllKeys()
sort.Strings(keys)
for i := range keys {
if _, ok := ignore[keys[i]]; ok {
continue
}
defaultValue := v.GetString(keys[i])
if len(defaultValue) == 0 {
continue
}
k := strings.Replace(keys[i], ".", "_", -1)
fmt.Printf("%s_%s = %s\n", envPrefix, strings.ToUpper(k), defaultValue)
}
fmt.Println()
fmt.Println("Peers preset:")
fmt.Println()
fmt.Printf("%s_%s_[N]_ADDRESS = string\n", envPrefix, strings.ToUpper(cfgPeers))
fmt.Printf("%s_%s_[N]_WEIGHT = 0..1 (float)\n", envPrefix, strings.ToUpper(cfgPeers))
os.Exit(0)
case versionFlag != nil && *versionFlag:
fmt.Printf("FrostFS S3 Gateway\nVersion: %s\nGoVersion: %s\n", version.Version, runtime.Version())
os.Exit(0)
}
if err = readInConfig(v); err != nil {
panic(err)
}
return &appCfg{
flags: flags,
settings: v,
}
}
func setDefaults(v *viper.Viper, flags *pflag.FlagSet) {
v.SetDefault(cfgAccessBoxCacheRemovingCheckInterval, defaultAccessBoxCacheRemovingCheckInterval)
// logger:
@ -950,8 +1126,6 @@ func newSettings() *viper.Viper {
// pool:
v.SetDefault(cfgPoolErrorThreshold, defaultPoolErrorThreshold)
v.SetDefault(cfgStreamTimeout, defaultStreamTimeout)
v.SetDefault(cfgPoolCbThreshold, defaultCbThreshold)
v.SetDefault(cfgPoolCbBreakDuration, defaultCbBreakDuration)
v.SetDefault(cfgPProfAddress, "localhost:8085")
v.SetDefault(cfgPrometheusAddress, "localhost:8086")
@ -994,78 +1168,21 @@ func newSettings() *viper.Viper {
// multinet
v.SetDefault(cfgMultinetFallbackDelay, defaultMultinetFallbackDelay)
// Bind flags
if err := bindFlags(v, flags); err != nil {
panic(fmt.Errorf("bind flags: %w", err))
if resolveMethods, err := flags.GetStringSlice(cfgResolveOrder); err == nil {
v.SetDefault(cfgResolveOrder, resolveMethods)
}
if err := flags.Parse(os.Args); err != nil {
panic(err)
}
if v.IsSet(cfgServer+".0."+cfgTLSKeyFile) && v.IsSet(cfgServer+".0."+cfgTLSCertFile) {
v.Set(cfgServer+".0."+cfgTLSEnabled, true)
}
if resolveMethods != nil {
v.SetDefault(cfgResolveOrder, *resolveMethods)
}
if peers != nil && len(*peers) > 0 {
for i := range *peers {
v.SetDefault(cfgPeers+"."+strconv.Itoa(i)+".address", (*peers)[i])
if peers, err := flags.GetStringArray(cfgPeers); err == nil {
for i := range peers {
v.SetDefault(cfgPeers+"."+strconv.Itoa(i)+".address", peers[i])
v.SetDefault(cfgPeers+"."+strconv.Itoa(i)+".weight", 1)
v.SetDefault(cfgPeers+"."+strconv.Itoa(i)+".priority", 1)
}
}
if domains != nil && len(*domains) > 0 {
v.SetDefault(cfgListenDomains, *domains)
if domains, err := flags.GetStringSlice(cfgListenDomains); err == nil && len(domains) > 0 {
v.SetDefault(cfgListenDomains, domains)
}
switch {
case help != nil && *help:
fmt.Printf("FrostFS S3 gateway %s\n", version.Version)
flags.PrintDefaults()
fmt.Println()
fmt.Println("Default environments:")
fmt.Println()
keys := v.AllKeys()
sort.Strings(keys)
for i := range keys {
if _, ok := ignore[keys[i]]; ok {
continue
}
defaultValue := v.GetString(keys[i])
if len(defaultValue) == 0 {
continue
}
k := strings.Replace(keys[i], ".", "_", -1)
fmt.Printf("%s_%s = %s\n", envPrefix, strings.ToUpper(k), defaultValue)
}
fmt.Println()
fmt.Println("Peers preset:")
fmt.Println()
fmt.Printf("%s_%s_[N]_ADDRESS = string\n", envPrefix, strings.ToUpper(cfgPeers))
fmt.Printf("%s_%s_[N]_WEIGHT = 0..1 (float)\n", envPrefix, strings.ToUpper(cfgPeers))
os.Exit(0)
case versionFlag != nil && *versionFlag:
fmt.Printf("FrostFS S3 Gateway\nVersion: %s\nGoVersion: %s\n", version.Version, runtime.Version())
os.Exit(0)
}
if err := readInConfig(v); err != nil {
panic(err)
}
return v
}
func bindFlags(v *viper.Viper, flags *pflag.FlagSet) error {
@ -1183,129 +1300,19 @@ type LoggerAppSettings interface {
DroppedLogsInc()
}
func pickLogger(v *viper.Viper, settings LoggerAppSettings) *Logger {
lvl, err := getLogLevel(v)
if err != nil {
panic(err)
}
dest := v.GetString(cfgLoggerDestination)
switch dest {
case destinationStdout:
return newStdoutLogger(v, lvl, settings)
case destinationJournald:
return newJournaldLogger(v, lvl, settings)
default:
panic(fmt.Sprintf("wrong destination for logger: %s", dest))
}
}
// newStdoutLogger constructs a Logger instance for the current application.
// Panics on failure.
//
// Logger contains a logger is built from zap's production logging configuration with:
// - parameterized level (debug by default)
// - console encoding
// - ISO8601 time encoding
// - sampling intervals
//
// and atomic log level to dynamically change it.
//
// Logger records a stack trace for all messages at or above fatal level.
//
// See also zapcore.Level, zap.NewProductionConfig, zap.AddStacktrace.
func newStdoutLogger(v *viper.Viper, lvl zapcore.Level, settings LoggerAppSettings) *Logger {
stdout := zapcore.AddSync(os.Stderr)
level := zap.NewAtomicLevelAt(lvl)
consoleOutCore := zapcore.NewCore(newLogEncoder(), stdout, level)
consoleOutCore = applyZapCoreMiddlewares(consoleOutCore, v, settings)
return &Logger{
logger: zap.New(consoleOutCore, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel))),
lvl: level,
}
}
func newJournaldLogger(v *viper.Viper, lvl zapcore.Level, settings LoggerAppSettings) *Logger {
level := zap.NewAtomicLevelAt(lvl)
encoder := zapjournald.NewPartialEncoder(newLogEncoder(), zapjournald.SyslogFields)
core := zapjournald.NewCore(level, encoder, &journald.Journal{}, zapjournald.SyslogFields)
coreWithContext := core.With([]zapcore.Field{
zapjournald.SyslogFacility(zapjournald.LogDaemon),
zapjournald.SyslogIdentifier(),
zapjournald.SyslogPid(),
})
coreWithContext = applyZapCoreMiddlewares(coreWithContext, v, settings)
l := zap.New(coreWithContext, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel)))
return &Logger{
logger: l,
lvl: level,
}
}
func newLogEncoder() zapcore.Encoder {
c := zap.NewProductionEncoderConfig()
c.EncodeTime = zapcore.ISO8601TimeEncoder
return zapcore.NewConsoleEncoder(c)
}
func applyZapCoreMiddlewares(core zapcore.Core, v *viper.Viper, settings LoggerAppSettings) zapcore.Core {
if v.GetBool(cfgLoggerSamplingEnabled) {
core = zapcore.NewSamplerWithOptions(core,
v.GetDuration(cfgLoggerSamplingInterval),
v.GetInt(cfgLoggerSamplingInitial),
v.GetInt(cfgLoggerSamplingThereafter),
zapcore.SamplerHook(func(_ zapcore.Entry, dec zapcore.SamplingDecision) {
if dec&zapcore.LogDropped > 0 {
settings.DroppedLogsInc()
}
}))
}
return core
}
func getLogLevel(v *viper.Viper) (zapcore.Level, error) {
var lvl zapcore.Level
lvlStr := v.GetString(cfgLoggerLevel)
err := lvl.UnmarshalText([]byte(lvlStr))
if err != nil {
return lvl, fmt.Errorf("incorrect logger level configuration %s (%v), "+
"value should be one of %v", lvlStr, err, [...]zapcore.Level{
zapcore.DebugLevel,
zapcore.InfoLevel,
zapcore.WarnLevel,
zapcore.ErrorLevel,
zapcore.DPanicLevel,
zapcore.PanicLevel,
zapcore.FatalLevel,
})
}
return lvl, nil
}
func validateDomains(domains []string, log *zap.Logger) []string {
validDomains := make([]string, 0, len(domains))
LOOP:
for _, domain := range domains {
if strings.Contains(domain, ":") {
log.Warn(logs.WarnDomainContainsPort, zap.String("domain", domain))
log.Warn(logs.WarnDomainContainsPort, zap.String("domain", domain), logs.TagField(logs.TagApp))
continue
}
domainParts := strings.Split(domain, ".")
for _, part := range domainParts {
if strings.ContainsAny(part, "<>") && part != wildcardPlaceholder {
log.Warn(logs.WarnDomainContainsInvalidPlaceholder, zap.String("domain", domain))
log.Warn(logs.WarnDomainContainsInvalidPlaceholder, zap.String("domain", domain), logs.TagField(logs.TagApp))
continue LOOP
}
}

View file

@ -0,0 +1,59 @@
package main
import (
"os"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/resolver"
"github.com/stretchr/testify/require"
)
func TestConfigReload(t *testing.T) {
f, err := os.CreateTemp("", "conf")
require.NoError(t, err)
defer func() {
require.NoError(t, os.Remove(f.Name()))
}()
confData := `
pprof:
enabled: true
frostfsid:
contract: name.nns
resolve_order:
- nns
`
_, err = f.WriteString(confData)
require.NoError(t, err)
require.NoError(t, f.Close())
cfg := newSettings()
require.NoError(t, cfg.flags.Parse([]string{"--config", f.Name(), "--max_clients_count", "10"}))
require.NoError(t, cfg.reload())
require.True(t, cfg.config().GetBool(cfgPProfEnabled))
require.Equal(t, "name.nns", cfg.config().GetString(cfgFrostfsIDContract))
require.Equal(t, []string{resolver.NNSResolver}, cfg.config().GetStringSlice(cfgResolveOrder))
require.Equal(t, 10, cfg.config().GetInt(cfgMaxClientsCount))
require.NoError(t, os.Truncate(f.Name(), 0))
require.NoError(t, cfg.reload())
require.False(t, cfg.config().GetBool(cfgPProfEnabled))
require.Equal(t, "frostfsid.frostfs", cfg.config().GetString(cfgFrostfsIDContract))
require.Equal(t, []string{resolver.DNSResolver}, cfg.config().GetStringSlice(cfgResolveOrder))
require.Equal(t, 10, cfg.config().GetInt(cfgMaxClientsCount))
}
func TestSetTLSEnabled(t *testing.T) {
cfg := newSettings()
require.NoError(t, cfg.flags.Parse([]string{"--" + cfgTLSCertFile, "tls.crt", "--" + cfgTLSKeyFile, "tls.key"}))
require.NoError(t, cfg.reload())
require.True(t, cfg.config().GetBool(cfgServer+".0."+cfgTLSEnabled))
}

210
cmd/s3-gw/logger.go Normal file
View file

@ -0,0 +1,210 @@
package main
import (
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
"git.frostfs.info/TrueCloudLab/zapjournald"
"github.com/spf13/viper"
"github.com/ssgreg/journald"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
const (
destinationStdout string = "stdout"
destinationJournald string = "journald"
)
var defaultTags = []string{logs.TagApp, logs.TagDatapath, logs.TagExternalStorage, logs.TagExternalStorageTree, logs.TagExternalBlockchain}
type Logger struct {
logger *zap.Logger
lvl zap.AtomicLevel
}
func pickLogger(v *viper.Viper, loggerSettings LoggerAppSettings, tagSettings TagFilterSettings) *Logger {
dest := v.GetString(cfgLoggerDestination)
switch dest {
case destinationStdout:
return newStdoutLogger(v, loggerSettings, tagSettings)
case destinationJournald:
return newJournaldLogger(v, loggerSettings, tagSettings)
default:
panic(fmt.Sprintf("wrong destination for logger: %s", dest))
}
}
// newStdoutLogger constructs a Logger instance for the current application.
// Panics on failure.
//
// Logger contains a logger is built from zap's production logging configuration with:
// - parameterized level (debug by default)
// - console encoding
// - ISO8601 time encoding
// - sampling intervals
//
// and atomic log level to dynamically change it.
//
// Logger records a stack trace for all messages at or above fatal level.
//
// See also zapcore.Level, zap.NewProductionConfig, zap.AddStacktrace.
func newStdoutLogger(v *viper.Viper, loggerSettings LoggerAppSettings, tagSettings TagFilterSettings) *Logger {
c := newZapLogConfig(v)
out, errSink, err := openZapSinks(c)
if err != nil {
panic(fmt.Sprintf("open zap sinks: %v", err.Error()))
}
core := zapcore.NewCore(zapcore.NewConsoleEncoder(c.EncoderConfig), out, c.Level)
core = applyZapCoreMiddlewares(core, v, loggerSettings, tagSettings)
l := zap.New(core, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel)), zap.ErrorOutput(errSink))
return &Logger{logger: l, lvl: c.Level}
}
func newJournaldLogger(v *viper.Viper, logSettings LoggerAppSettings, tagSettings TagFilterSettings) *Logger {
c := newZapLogConfig(v)
// We can use NewJSONEncoder instead if, say, frontend
// would like to access journald logs and parse them easily.
encoder := zapjournald.NewPartialEncoder(zapcore.NewConsoleEncoder(c.EncoderConfig), zapjournald.SyslogFields)
journalCore := zapjournald.NewCore(c.Level, encoder, &journald.Journal{}, zapjournald.SyslogFields)
core := journalCore.With([]zapcore.Field{
zapjournald.SyslogFacility(zapjournald.LogDaemon),
zapjournald.SyslogIdentifier(),
zapjournald.SyslogPid(),
})
core = applyZapCoreMiddlewares(core, v, logSettings, tagSettings)
l := zap.New(core, zap.AddStacktrace(zap.NewAtomicLevelAt(zap.FatalLevel)))
return &Logger{logger: l, lvl: c.Level}
}
func openZapSinks(cfg zap.Config) (zapcore.WriteSyncer, zapcore.WriteSyncer, error) {
sink, closeOut, err := zap.Open(cfg.OutputPaths...)
if err != nil {
return nil, nil, err
}
errSink, _, err := zap.Open(cfg.ErrorOutputPaths...)
if err != nil {
closeOut()
return nil, nil, err
}
return sink, errSink, nil
}
var _ zapcore.Core = (*zapCoreTagFilterWrapper)(nil)
type zapCoreTagFilterWrapper struct {
core zapcore.Core
settings TagFilterSettings
extra []zap.Field
}
type TagFilterSettings interface {
LevelEnabled(tag string, lvl zapcore.Level) bool
}
func (c *zapCoreTagFilterWrapper) Enabled(level zapcore.Level) bool {
return c.core.Enabled(level)
}
func (c *zapCoreTagFilterWrapper) With(fields []zapcore.Field) zapcore.Core {
return &zapCoreTagFilterWrapper{
core: c.core.With(fields),
settings: c.settings,
extra: append(c.extra, fields...),
}
}
func (c *zapCoreTagFilterWrapper) Check(entry zapcore.Entry, checked *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if c.core.Enabled(entry.Level) {
return checked.AddCore(entry, c)
}
return checked
}
func (c *zapCoreTagFilterWrapper) Write(entry zapcore.Entry, fields []zapcore.Field) error {
if c.shouldSkip(entry, fields) || c.shouldSkip(entry, c.extra) {
return nil
}
return c.core.Write(entry, fields)
}
func (c *zapCoreTagFilterWrapper) shouldSkip(entry zapcore.Entry, fields []zap.Field) bool {
for _, field := range fields {
if field.Key == logs.TagFieldName && field.Type == zapcore.StringType {
if !c.settings.LevelEnabled(field.String, entry.Level) {
return true
}
break
}
}
return false
}
func (c *zapCoreTagFilterWrapper) Sync() error {
return c.core.Sync()
}
func applyZapCoreMiddlewares(core zapcore.Core, v *viper.Viper, appSettings LoggerAppSettings, tagSettings TagFilterSettings) zapcore.Core {
core = &zapCoreTagFilterWrapper{
core: core,
settings: tagSettings,
}
if v.GetBool(cfgLoggerSamplingEnabled) {
core = zapcore.NewSamplerWithOptions(core,
v.GetDuration(cfgLoggerSamplingInterval),
v.GetInt(cfgLoggerSamplingInitial),
v.GetInt(cfgLoggerSamplingThereafter),
zapcore.SamplerHook(func(_ zapcore.Entry, dec zapcore.SamplingDecision) {
if dec&zapcore.LogDropped > 0 {
appSettings.DroppedLogsInc()
}
}))
}
return core
}
func newZapLogConfig(v *viper.Viper) zap.Config {
lvl, err := getLogLevel(v)
if err != nil {
panic(err)
}
c := zap.Config{
Level: zap.NewAtomicLevelAt(lvl),
EncoderConfig: zap.NewProductionEncoderConfig(),
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
}
c.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
return c
}
func getLogLevel(v *viper.Viper) (zapcore.Level, error) {
var lvl zapcore.Level
lvlStr := v.GetString(cfgLoggerLevel)
err := lvl.UnmarshalText([]byte(lvlStr))
if err != nil {
return lvl, fmt.Errorf("incorrect logger level configuration %s (%v), "+
"value should be one of %v", lvlStr, err, [...]zapcore.Level{
zapcore.DebugLevel,
zapcore.InfoLevel,
zapcore.WarnLevel,
zapcore.ErrorLevel,
zapcore.DPanicLevel,
zapcore.PanicLevel,
zapcore.FatalLevel,
})
}
return lvl, nil
}

View file

@ -8,9 +8,9 @@ import (
func main() {
g, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
v := newSettings()
cfg := newSettings()
a := newApp(g, v)
a := newApp(g, cfg)
go a.Serve(g)

View file

@ -19,24 +19,24 @@ type Service struct {
// Start runs http service with the exposed endpoint on the configured port.
func (ms *Service) Start() {
if ms.enabled {
ms.log.Info(logs.ServiceIsRunning, zap.String("endpoint", ms.Addr))
ms.log.Info(logs.ServiceIsRunning, zap.String("endpoint", ms.Addr), logs.TagField(logs.TagApp))
err := ms.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
ms.log.Warn(logs.ServiceCouldntStartOnConfiguredPort)
ms.log.Warn(logs.ServiceCouldntStartOnConfiguredPort, logs.TagField(logs.TagApp))
}
} else {
ms.log.Info(logs.ServiceHasntStartedSinceItsDisabled)
ms.log.Info(logs.ServiceHasntStartedSinceItsDisabled, logs.TagField(logs.TagApp))
}
}
// ShutDown stops the service.
func (ms *Service) ShutDown(ctx context.Context) {
ms.log.Info(logs.ShuttingDownService, zap.String("endpoint", ms.Addr))
ms.log.Info(logs.ShuttingDownService, zap.String("endpoint", ms.Addr), logs.TagField(logs.TagApp))
err := ms.Shutdown(ctx)
if err != nil {
ms.log.Error(logs.CantGracefullyShutDownService, zap.Error(err))
ms.log.Error(logs.CantGracefullyShutDownService, zap.Error(err), logs.TagField(logs.TagApp))
if err = ms.Close(); err != nil {
ms.log.Panic(logs.CantShutDownService, zap.Error(err))
ms.log.Panic(logs.CantShutDownService, zap.Error(err), logs.TagField(logs.TagApp))
}
}
}

View file

@ -55,6 +55,11 @@ S3_GW_LOGGER_SAMPLING_ENABLED=false
S3_GW_LOGGER_SAMPLING_INITIAL=100
S3_GW_LOGGER_SAMPLING_THEREAFTER=100
S3_GW_LOGGER_SAMPLING_INTERVAL=1s
S3_GW_LOGGER_TAGS_0_NAME=app
S3_GW_LOGGER_TAGS_0_LEVEL=info
S3_GW_LOGGER_TAGS_1_NAME=datapath
S3_GW_LOGGER_TAGS_1_LEVEL=fatal
# HTTP logger
S3_GW_HTTP_LOGGING_ENABLED=false
@ -90,10 +95,6 @@ S3_GW_HEALTHCHECK_TIMEOUT=15s
S3_GW_REBALANCE_INTERVAL=60s
# The number of errors on connection after which node is considered as unhealthy
S3_GW_POOL_ERROR_THRESHOLD=100
# The number of init errors before tree service circuit breaker is closed
S3_GW_POOL_CB_THRESHOLD: 10
# Duration when circuit breaker blocks all tree service inits to remote node
S3_GW_POOL_CB_BREAK_DURATION: 10s
# Limits for processing of clients' requests
S3_GW_MAX_CLIENTS_COUNT=100
@ -192,6 +193,10 @@ S3_GW_KLUDGE_USE_DEFAULT_XMLNS=false
S3_GW_KLUDGE_BYPASS_CONTENT_ENCODING_CHECK_IN_CHUNKS=false
# Namespaces that should be handled as default
S3_GW_KLUDGE_DEFAULT_NAMESPACES="" "root"
# During listing the s3 gate may send whitespaces to client to prevent it from cancelling request.
# The gate is going to send whitespace every time it receives chunk of data from FrostFS storage.
# This parameter enables this feature and limits frequency of whitespace transmissions.
S3_GW_KLUDGE_LISTING_KEEPALIVE_THROTTLE=10s
# Kludge profiles
S3_GW_KLUDGE_PROFILE_0_USER_AGENT=aws-cli
S3_GW_KLUDGE_PROFILE_0_USE_DEFAULT_XMLNS=true

View file

@ -60,6 +60,12 @@ logger:
initial: 100
thereafter: 100
interval: 1s
tags:
- name: "app"
level: "debug"
- name: "datapath"
- name: "external_storage"
- name: "external_storage_tree"
# log http request data (URI, headers, query, etc)
http_logging:
@ -110,10 +116,6 @@ healthcheck_timeout: 15s
rebalance_interval: 60s
# The number of errors on connection after which node is considered as unhealthy
pool_error_threshold: 100
# The number of init errors before tree service circuit breaker is closed
pool_cb_threshold: 10
# Duration when circuit breaker blocks all tree service inits to remote node
pool_cb_break_duration: 10s
# Limits for processing of clients' requests
@ -232,6 +234,10 @@ kludge:
bypass_content_encoding_check_in_chunks: false
# Namespaces that should be handled as default
default_namespaces: [ "", "root" ]
# During listing the s3 gate may send whitespaces to client to prevent it from cancelling request.
# The gate is going to send whitespace every time it receives chunk of data from FrostFS storage.
# This parameter enables this feature and limits frequency of whitespace transmissions.
listing_keepalive_throttle: 10s
# new profile section override defaults based on user agent
profile:
- user_agent: aws-cli

View file

@ -202,7 +202,7 @@ func (c *cred) checkIfCredentialsAreRemoved(ctx context.Context, cnrID cid.ID, a
func (c *cred) putBoxToCache(accessKeyID string, val *cache.AccessBoxCacheValue) {
if err := c.cache.Put(accessKeyID, val); err != nil {
c.log.Warn(logs.CouldntPutAccessBoxIntoCache, zap.String("accessKeyID", accessKeyID))
c.log.Warn(logs.CouldntPutAccessBoxIntoCache, zap.String("accessKeyID", accessKeyID), logs.TagField(logs.TagDatapath))
}
}
@ -241,7 +241,7 @@ func (c *cred) getAccessBox(ctx context.Context, cnrID cid.ID, accessKeyID strin
func (c *cred) Put(ctx context.Context, prm CredentialsParam) (oid.Address, error) {
if prm.AccessKeyID != "" {
c.log.Info(logs.CheckCustomAccessKeyIDUniqueness, zap.String("access_key_id", prm.AccessKeyID))
c.log.Info(logs.CheckCustomAccessKeyIDUniqueness, zap.String("access_key_id", prm.AccessKeyID), logs.TagField(logs.TagApp))
credsPrm := PrmGetCredsObject{
Container: prm.Container,
AccessKeyID: prm.AccessKeyID,

View file

@ -64,6 +64,8 @@ $ frostfs-s3-gw --listen_address 192.168.130.130:443 \
Using these flag you can configure only one address. To set multiple addresses use yaml config.
**Note:** It's not recommended to configure addresses via flags and yaml config at the same time.
### RPC endpoint and resolving of bucket names
To set RPC endpoint specify a value of parameter `-r` or `--rpc_endpoint`. The parameter is **required if** another
@ -217,8 +219,6 @@ tree_stream_timeout: 10s
healthcheck_timeout: 15s
rebalance_interval: 60s
pool_error_threshold: 100
pool_cb_threshold: 10
pool_cb_break_duration: 10s
max_clients_count: 100
max_clients_deadline: 30s
@ -243,8 +243,6 @@ source_ip_header: "Source-Ip"
| `healthcheck_timeout` | `duration` | no | `15s` | Timeout to check node health during rebalance. |
| `rebalance_interval` | `duration` | no | `60s` | Interval to check node health. |
| `pool_error_threshold` | `uint32` | no | `100` | The number of errors on connection after which node is considered as unhealthy. |
| `pool_cb_threshold` | `int` | no | `10` | The number of init errors before tree service circuit breaker is closed |
| `pool_cb_break_timeout` | `duration` | no | `10s` | Duration when circuit breaker blocks all tree service inits to remote node |
| `max_clients_count` | `int` | no | `100` | Limits for processing of clients' requests. |
| `max_clients_deadline` | `duration` | no | `30s` | Deadline after which the gate sends error `RequestTimeout` to a client. |
| `allowed_access_key_id_prefixes` | `[]string` | no | | List of allowed `AccessKeyID` prefixes which S3 GW serve. If the parameter is omitted, all `AccessKeyID` will be accepted. |
@ -383,6 +381,13 @@ logger:
initial: 100
thereafter: 100
interval: 1s
tags:
- name: app
level: info
- name: datapath
- name: external_blockchain
- name: external_storage_tree
- name: external_storage
```
| Parameter | Type | SIGHUP reload | Default value | Description |
@ -394,6 +399,32 @@ logger:
| `sampling.thereafter` | `int` | no | '100' | Sampling count of entries after an `interval`. |
| `sampling.interval` | `duration` | no | '1s' | Sampling interval of messaging similar entries. |
## Tags
There are additional log entries that can hurt performance and can be additionally logged by using `logger.tags`
parameter.
If section `tags` isn't set the default tags (see [Tag values](#tag-values)) be enabled.
If section `tags` set but empty no tags be used.
```yaml
tags:
- name: "app"
level: info
```
| Parameter | Type | SIGHUP reload | Default value | Description |
|-----------------------|------------|---------------|---------------------------|-------------------------------------------------------------------------------------------------------|
| `name` | `string` | yes | | Tag name. Possible values see below in `Tag values` section. |
| `level` | `string` | yes | Value from `logger.level` | Logging level for specific tag. Possible values: `debug`, `info`, `warn`, `dpanic`, `panic`, `fatal`. |
### Tag values
* `app` - common application logs (enabled by default).
* `datapath` - main logic of application (enabled by default).
* `external_blockchain` - external interaction with neo-go blockchain (enabled by default).
* `external_storage` - external interaction with storage node (enabled by default).
* `external_storage_tree` - external interaction with tree service in storage node (enabled by default).
### `http_logging` section
@ -639,6 +670,7 @@ kludge:
use_default_xmlns: false
bypass_content_encoding_check_in_chunks: false
default_namespaces: [ "", "root" ]
listing_keepalive_throttle: 10s
profile:
- user_agent: aws-cli
use_default_xmlns: false
@ -647,12 +679,13 @@ kludge:
bypass_content_encoding_check_in_chunks: false
```
| Parameter | Type | SIGHUP reload | Default value | Description |
|-------------------------------------------|----------------------------------|---------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `use_default_xmlns` | `bool` | yes | `false` | Enable using default xml namespace `http://s3.amazonaws.com/doc/2006-03-01/` when parse xml bodies. |
| `bypass_content_encoding_check_in_chunks` | `bool` | yes | `false` | Use this flag to be able to use [chunked upload approach](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html) without having `aws-chunked` value in `Content-Encoding` header. |
| `default_namespaces` | `[]string` | yes | `["","root"]` | Namespaces that should be handled as default. |
| `profile` | [[]Profile](#profile-subsection) | yes | | An array of configurable profiles. |
| Parameter | Type | SIGHUP reload | Default value | Description |
|-------------------------------------------|----------------------------------|---------------|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `use_default_xmlns` | `bool` | yes | `false` | Enable using default xml namespace `http://s3.amazonaws.com/doc/2006-03-01/` when parse xml bodies. |
| `bypass_content_encoding_check_in_chunks` | `bool` | yes | `false` | Use this flag to be able to use [chunked upload approach](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html) without having `aws-chunked` value in `Content-Encoding` header. |
| `default_namespaces` | `[]string` | yes | `["","root"]` | Namespaces that should be handled as default. |
| `listing_keepalive_throttle` | `duration` | yes | `0` (means disabled) | During listing the s3 gate may send whitespaces to client to prevent it from cancelling request. The gate is going to send whitespace every time it receives chunk of data from FrostFS storage. This parameter enables this feature and limits frequency of whitespace transmissions. |
| `profile` | [[]Profile](#profile-subsection) | yes | | An array of configurable profiles. |
#### `profile` subsection
@ -830,7 +863,7 @@ containers:
| Parameter | Type | SIGHUP reload | Default value | Description |
|-------------|----------|---------------|---------------|-------------------------------------------------------------------------------------------------------------------------|
| `cors` | `string` | no | | Container name for CORS configurations. If not set, container of the bucket is used. |
| `cors` | `string` | no | | Container name for CORS configurations. |
| `lifecycle` | `string` | no | | Container name for lifecycle configurations. If not set, container of the bucket is used. |
| `accessbox` | `string` | no | | Container name to lookup accessbox if custom aws credentials is used. If not set, custom credentials are not supported. |

5
go.mod
View file

@ -4,8 +4,9 @@ go 1.22
require (
git.frostfs.info/TrueCloudLab/frostfs-contract v0.20.1-0.20241022094040-5f956751d48b
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241112082307-f17779933e88
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20250305114045-7a37613988a4
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241125133852-37bd75821121
git.frostfs.info/TrueCloudLab/frostfs-qos v0.0.0-20250227072915-25102d1e1aa3
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20250228093256-2b8329e026c7
git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240822104152-a3bc3099bd5b
git.frostfs.info/TrueCloudLab/zapjournald v0.0.0-20240124114243-cb2e66427d02

10
go.sum
View file

@ -40,10 +40,12 @@ git.frostfs.info/TrueCloudLab/frostfs-contract v0.20.1-0.20241022094040-5f956751
git.frostfs.info/TrueCloudLab/frostfs-contract v0.20.1-0.20241022094040-5f956751d48b/go.mod h1:5fSm/l5xSjGWqsPUffSdboiGFUHa7y/1S0fvxzQowN8=
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-observability v0.0.0-20241112082307-f17779933e88 h1:9bvBDLApbbO5sXBKdODpE9tzy3HV99nXxkDWNn22rdI=
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241112082307-f17779933e88/go.mod h1:kbwB4v2o6RyOfCo9kEFeUDZIX3LKhmS0yXPrtvzkQ1g=
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20250305114045-7a37613988a4 h1:DWMwf08GhGE9Q2g3p8Kyjl0DxPuxY7WmtkkVf4iBiCo=
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20250305114045-7a37613988a4/go.mod h1:aQpPWfG8oyfJ2X+FenPTJpSRWZjwcP5/RAtkW+/VEX8=
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241125133852-37bd75821121 h1:/Z8DfbLZXp7exUQWUKoG/9tbFdI9d5lV1qSReaYoG8I=
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241125133852-37bd75821121/go.mod h1:kbwB4v2o6RyOfCo9kEFeUDZIX3LKhmS0yXPrtvzkQ1g=
git.frostfs.info/TrueCloudLab/frostfs-qos v0.0.0-20250227072915-25102d1e1aa3 h1:QnAt5b2R6+hQthMOIn5ECfLAlVD8IAE5JRm1NCCOmuE=
git.frostfs.info/TrueCloudLab/frostfs-qos v0.0.0-20250227072915-25102d1e1aa3/go.mod h1:PCijYq4oa8vKtIEcUX6jRiszI6XAW+nBwU+T1kB4d1U=
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20250228093256-2b8329e026c7 h1:T7r38zZ/aT1xTp+AxhizfukW10Rq3WQ5/m3moLGVnSk=
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20250228093256-2b8329e026c7/go.mod h1:aQpPWfG8oyfJ2X+FenPTJpSRWZjwcP5/RAtkW+/VEX8=
git.frostfs.info/TrueCloudLab/hrw v1.2.1 h1:ccBRK21rFvY5R1WotI6LNoPlizk7qSvdfD8lNIRudVc=
git.frostfs.info/TrueCloudLab/hrw v1.2.1/go.mod h1:C1Ygde2n843yTZEQ0FP69jYiuaYV0kriLvP4zm8JuvM=
git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972 h1:/960fWeyn2AFHwQUwDsWB3sbP6lTEnFnMzLMM6tx6N8=

View file

@ -10,11 +10,13 @@ import (
"strings"
"time"
qostagging "git.frostfs.info/TrueCloudLab/frostfs-qos/tagging"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/crdt"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/util"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
objectv2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
@ -153,7 +155,7 @@ func (x *AuthmateFrostFS) GetCredsObject(ctx context.Context, prm tokens.PrmGetC
}
func (x *AuthmateFrostFS) readObject(ctx context.Context, addr oid.Address) (*object.Object, error) {
res, err := x.frostFS.GetObject(ctx, frostfs.PrmObjectGet{
res, err := x.frostFS.GetObject(qostagging.ContextWithIOTag(ctx, util.InternalIOTag), frostfs.PrmObjectGet{
Container: addr.Container(),
Object: addr.Object(),
})
@ -211,7 +213,7 @@ func (x *AuthmateFrostFS) CreateObject(ctx context.Context, prm tokens.PrmObject
attributes = append(attributes, [2]string{attr.Key(), attr.Value()})
}
res, err := x.frostFS.CreateObject(ctx, frostfs.PrmObjectCreate{
res, err := x.frostFS.CreateObject(qostagging.ContextWithIOTag(ctx, util.InternalIOTag), frostfs.PrmObjectCreate{
Container: prm.Container,
Filepath: prm.Filepath,
Attributes: attributes,
@ -225,7 +227,7 @@ func (x *AuthmateFrostFS) CreateObject(ctx context.Context, prm tokens.PrmObject
}
func (x *AuthmateFrostFS) getCredVersions(ctx context.Context, cnrID cid.ID, accessKeyID string) (*crdt.ObjectVersions, error) {
credVersions, err := x.frostFS.SearchObjects(ctx, frostfs.PrmObjectSearch{
credVersions, err := x.frostFS.SearchObjects(qostagging.ContextWithIOTag(ctx, util.InternalIOTag), frostfs.PrmObjectSearch{
Container: cnrID,
ExactAttribute: [2]string{accessBoxCRDTNameAttr, accessKeyID},
})

View file

@ -17,6 +17,7 @@ type ObjectVersions struct {
objects []*ObjectVersion
addList []string
isSorted bool
less func(*ObjectVersion, *ObjectVersion) bool
}
type ObjectVersion struct {
@ -30,7 +31,7 @@ func (o *ObjectVersion) VersionID() string {
}
func NewObjectVersions(name string) *ObjectVersions {
return &ObjectVersions{name: name}
return &ObjectVersions{name: name, less: less}
}
func NewObjectVersion(obj *object.Object) *ObjectVersion {
@ -86,6 +87,15 @@ func (v *ObjectVersions) GetLast() *ObjectVersion {
return v.objects[len(v.objects)-1]
}
func (v *ObjectVersions) GetSorted() []*ObjectVersion {
v.sort()
return v.objects
}
func (v *ObjectVersions) SetLessFunc(less func(*ObjectVersion, *ObjectVersion) bool) {
v.less = less
}
func splitVersions(header string) []string {
if len(header) == 0 {
return nil
@ -97,7 +107,7 @@ func splitVersions(header string) []string {
func (v *ObjectVersions) sort() {
if !v.isSorted {
sort.Slice(v.objects, func(i, j int) bool {
return less(v.objects[i], v.objects[j])
return v.less(v.objects[i], v.objects[j])
})
v.isSorted = true
}

View file

@ -9,6 +9,7 @@ import (
"strings"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
frosterr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/util"
@ -57,6 +58,9 @@ func NewFrostFS(p *pool.Pool, key *keys.PrivateKey) *FrostFS {
// TimeToEpoch implements layer.FrostFS interface method.
func (x *FrostFS) TimeToEpoch(ctx context.Context, now, futureTime time.Time) (uint64, uint64, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "frostfs.TimeToEpoch")
defer span.End()
if futureTime.Before(now) {
return 0, 0, fmt.Errorf("time '%s' must be in the future (after %s)",
futureTime.Format(time.RFC3339), now.Format(time.RFC3339))
@ -77,6 +81,9 @@ func (x *FrostFS) TimeToEpoch(ctx context.Context, now, futureTime time.Time) (u
// Container implements layer.FrostFS interface method.
func (x *FrostFS) Container(ctx context.Context, layerPrm frostfs.PrmContainer) (*container.Container, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "frostfs.Container")
defer span.End()
prm := pool.PrmContainerGet{
ContainerID: layerPrm.ContainerID,
Session: layerPrm.SessionToken,
@ -92,6 +99,9 @@ func (x *FrostFS) Container(ctx context.Context, layerPrm frostfs.PrmContainer)
// CreateContainer implements layer.FrostFS interface method.
func (x *FrostFS) CreateContainer(ctx context.Context, prm frostfs.PrmContainerCreate) (*frostfs.ContainerCreateResult, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "frostfs.CreateContainer")
defer span.End()
var cnr container.Container
cnr.Init()
cnr.SetPlacementPolicy(prm.Policy)
@ -139,6 +149,9 @@ func (x *FrostFS) CreateContainer(ctx context.Context, prm frostfs.PrmContainerC
// AddContainerPolicyChain implements frostfs.FrostFS interface method.
func (x *FrostFS) AddContainerPolicyChain(ctx context.Context, prm frostfs.PrmAddContainerPolicyChain) error {
ctx, span := tracing.StartSpanFromContext(ctx, "frostfs.AddContainerPolicyChain")
defer span.End()
data, err := prm.Chain.MarshalBinary()
if err != nil {
return err
@ -158,6 +171,9 @@ func (x *FrostFS) AddContainerPolicyChain(ctx context.Context, prm frostfs.PrmAd
// UserContainers implements layer.FrostFS interface method.
func (x *FrostFS) UserContainers(ctx context.Context, layerPrm frostfs.PrmUserContainers) ([]cid.ID, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "frostfs.UserContainers")
defer span.End()
prm := pool.PrmContainerList{
OwnerID: layerPrm.UserID,
Session: layerPrm.SessionToken,
@ -169,6 +185,9 @@ func (x *FrostFS) UserContainers(ctx context.Context, layerPrm frostfs.PrmUserCo
// DeleteContainer implements layer.FrostFS interface method.
func (x *FrostFS) DeleteContainer(ctx context.Context, id cid.ID, token *session.Container) error {
ctx, span := tracing.StartSpanFromContext(ctx, "frostfs.DeleteContainer")
defer span.End()
prm := pool.PrmContainerDelete{ContainerID: id, Session: token, WaitParams: &x.await}
err := x.pool.DeleteContainer(ctx, prm)
@ -177,6 +196,9 @@ func (x *FrostFS) DeleteContainer(ctx context.Context, id cid.ID, token *session
// CreateObject implements layer.FrostFS interface method.
func (x *FrostFS) CreateObject(ctx context.Context, prm frostfs.PrmObjectCreate) (*frostfs.CreateObjectResult, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "frostfs.CreateObject")
defer span.End()
attrNum := len(prm.Attributes) + 1 // + creation time
if prm.Filepath != "" {
@ -271,6 +293,9 @@ func (x payloadReader) Read(p []byte) (int, error) {
// HeadObject implements layer.FrostFS interface method.
func (x *FrostFS) HeadObject(ctx context.Context, prm frostfs.PrmObjectHead) (*object.Object, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "frostfs.HeadObject")
defer span.End()
var addr oid.Address
addr.SetContainer(prm.Container)
addr.SetObject(prm.Object)
@ -294,6 +319,9 @@ func (x *FrostFS) HeadObject(ctx context.Context, prm frostfs.PrmObjectHead) (*o
// GetObject implements layer.FrostFS interface method.
func (x *FrostFS) GetObject(ctx context.Context, prm frostfs.PrmObjectGet) (*frostfs.Object, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "frostfs.GetObject")
defer span.End()
var addr oid.Address
addr.SetContainer(prm.Container)
addr.SetObject(prm.Object)
@ -320,6 +348,9 @@ func (x *FrostFS) GetObject(ctx context.Context, prm frostfs.PrmObjectGet) (*fro
// RangeObject implements layer.FrostFS interface method.
func (x *FrostFS) RangeObject(ctx context.Context, prm frostfs.PrmObjectRange) (io.ReadCloser, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "frostfs.RangeObject")
defer span.End()
var addr oid.Address
addr.SetContainer(prm.Container)
addr.SetObject(prm.Object)
@ -345,6 +376,9 @@ func (x *FrostFS) RangeObject(ctx context.Context, prm frostfs.PrmObjectRange) (
// DeleteObject implements layer.FrostFS interface method.
func (x *FrostFS) DeleteObject(ctx context.Context, prm frostfs.PrmObjectDelete) error {
ctx, span := tracing.StartSpanFromContext(ctx, "frostfs.DeleteObject")
defer span.End()
var addr oid.Address
addr.SetContainer(prm.Container)
addr.SetObject(prm.Object)
@ -364,6 +398,9 @@ func (x *FrostFS) DeleteObject(ctx context.Context, prm frostfs.PrmObjectDelete)
// SearchObjects implements layer.FrostFS interface method.
func (x *FrostFS) SearchObjects(ctx context.Context, prm frostfs.PrmObjectSearch) ([]oid.ID, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "frostfs.SearchObjects")
defer span.End()
filters := object.NewSearchFilters()
filters.AddRootFilter()
@ -401,6 +438,9 @@ func (x *FrostFS) SearchObjects(ctx context.Context, prm frostfs.PrmObjectSearch
// NetworkInfo implements layer.FrostFS interface method.
func (x *FrostFS) NetworkInfo(ctx context.Context) (netmap.NetworkInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "frostfs.NetworkInfo")
defer span.End()
ni, err := x.pool.NetworkInfo(ctx)
if err != nil {
return ni, handleObjectError("get network info via connection pool", err)
@ -410,6 +450,9 @@ func (x *FrostFS) NetworkInfo(ctx context.Context) (netmap.NetworkInfo, error) {
}
func (x *FrostFS) NetmapSnapshot(ctx context.Context) (netmap.NetMap, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "frostfs.NetmapSnapshot")
defer span.End()
netmapSnapshot, err := x.pool.NetMapSnapshot(ctx)
if err != nil {
return netmapSnapshot, handleObjectError("get netmap via connection pool", err)
@ -419,6 +462,9 @@ func (x *FrostFS) NetmapSnapshot(ctx context.Context) (netmap.NetMap, error) {
}
func (x *FrostFS) PatchObject(ctx context.Context, prm frostfs.PrmObjectPatch) (oid.ID, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "frostfs.PatchObject")
defer span.End()
var addr oid.Address
addr.SetContainer(prm.Container)
addr.SetObject(prm.Object)
@ -467,6 +513,9 @@ func NewResolverFrostFS(p *pool.Pool) *ResolverFrostFS {
// SystemDNS implements resolver.FrostFS interface method.
func (x *ResolverFrostFS) SystemDNS(ctx context.Context) (string, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "frostfs.SystemDNS")
defer span.End()
networkInfo, err := x.pool.NetworkInfo(ctx)
if err != nil {
return "", handleObjectError("read network info via client", err)

View file

@ -61,7 +61,7 @@ func (f *FrostFSID) GetUserGroupIDsAndClaims(userHash util.Uint160) ([]string, m
subj, err := f.getSubject(userHash)
if err != nil {
if strings.Contains(err.Error(), "not found") {
f.log.Debug(logs.UserGroupsListIsEmpty, zap.Error(err))
f.log.Debug(logs.UserGroupsListIsEmpty, zap.Error(err), logs.TagField(logs.TagExternalBlockchain))
return nil, nil, nil
}
return nil, nil, err
@ -86,7 +86,7 @@ func (f *FrostFSID) getSubject(addr util.Uint160) (*client.SubjectExtended, erro
}
if err = f.cache.PutSubject(addr, subj); err != nil {
f.log.Warn(logs.CouldntCacheSubject, zap.Error(err))
f.log.Warn(logs.CouldntCacheSubject, zap.Error(err), logs.TagField(logs.TagDatapath))
}
return subj, nil
@ -121,7 +121,7 @@ func (f *FrostFSID) getUserKey(namespace, name string) (*keys.PublicKey, error)
}
if err = f.cache.PutUserKey(namespace, name, userKey); err != nil {
f.log.Warn(logs.CouldntCacheUserKey, zap.Error(err))
f.log.Warn(logs.CouldntCacheUserKey, zap.Error(err), logs.TagField(logs.TagDatapath))
}
return userKey, nil

View file

@ -69,7 +69,7 @@ func (c *MorphRuleChainStorage) ListMorphRuleChains(name chain.Name, target engi
}
if err = c.cache.Put(key, list); err != nil {
c.log.Warn(logs.CouldntCacheListPolicyChains)
c.log.Warn(logs.CouldntCacheListPolicyChains, logs.TagField(logs.TagApp))
}
return list, nil

View file

@ -13,6 +13,8 @@ import (
"github.com/nspcc-dev/neo-go/pkg/util"
)
const InternalIOTag = "internal"
// ResolveContractHash determine contract hash by resolving NNS name.
func ResolveContractHash(contractHash, rpcAddress string) (util.Uint160, error) {
if hash, err := util.Uint160DecodeStringLE(contractHash); err == nil {

View file

@ -1,189 +1,231 @@
package logs
import "go.uber.org/zap"
const (
RequestUnmatched = "request unmatched" // Error in ../../api/router.go
CheckContainer = "check container" // Info in ../../authmate/authmate.go
CreateContainer = "create container" // Info in ../../authmate/authmate.go
StoreBearerTokenIntoFrostFS = "store bearer token into FrostFS" // Info in ../../authmate/authmate.go
UpdateAccessCredObjectIntoFrostFS = "update access cred object into FrostFS" // Info in ../../authmate/authmate.go
MetricsAreDisabled = "metrics are disabled" // Warn in ../../metrics/app.go
FoundMoreThanOneUnversionedNode = "found more than one unversioned node" // Debug in ../../pkg/service/tree/tree.go
ServiceIsRunning = "service is running" // Info in ../../cmd/s3-gw/service.go
ServiceCouldntStartOnConfiguredPort = "service couldn't start on configured port" // Warn in ../../cmd/s3-gw/service.go
ServiceHasntStartedSinceItsDisabled = "service hasn't started since it's disabled" // Info in ../../cmd/s3-gw/service.go
ShuttingDownService = "shutting down service" // Info in ../../cmd/s3-gw/service.go
CantGracefullyShutDownService = "can't gracefully shut down service, force stop" // Error in ../../cmd/s3-gw/service.go
ContainerResolverWillBeDisabled = "container resolver will be disabled because of resolvers 'resolver_order' is empty" // Info in ../../cmd/s3-gw/app.go
FailedToInitializeTracing = "failed to initialize tracing" // Warn in ../../cmd/s3-gw/app.go
TracingConfigUpdated = "tracing config updated" // Info in ../../cmd/s3-gw/app.go
FailedToShutdownTracing = "failed to shutdown tracing" // Warn in ../../cmd/s3-gw/app.go
UsingCredentials = "using credentials" // Info in ../../cmd/s3-gw/app.go
ApplicationStarted = "application started" // Info in ../../cmd/s3-gw/app.go
ApplicationFinished = "application finished" // Info in ../../cmd/s3-gw/app.go
StartingServer = "starting server" // Info in ../../cmd/s3-gw/app.go
StoppingServer = "stopping server" // Info in ../../cmd/s3-gw/app.go
SIGHUPConfigReloadStarted = "SIGHUP config reload started" // Info in ../../cmd/s3-gw/app.go
FailedToReloadConfigBecauseItsMissed = "failed to reload config because it's missed" // Warn in ../../cmd/s3-gw/app.go
FailedToReloadConfig = "failed to reload config" // Warn in ../../cmd/s3-gw/app.go
FailedToReloadResolvers = "failed to reload resolvers" // Warn in ../../cmd/s3-gw/app.go
FailedToReloadServerParameters = "failed to reload server parameters" // Warn in ../../cmd/s3-gw/app.go
SIGHUPConfigReloadCompleted = "SIGHUP config reload completed" // Info in ../../cmd/s3-gw/app.go
LogLevelWontBeUpdated = "log level won't be updated" // Warn in ../../cmd/s3-gw/app.go
FailedToAddServer = "failed to add server" // Warn in ../../cmd/s3-gw/app.go
AddServer = "add server" // Info in ../../cmd/s3-gw/app.go
ResolverNNSWontBeUsedSinceRPCEndpointIsntProvided = "resolver 'nns' won't be used since 'rpc_endpoint' isn't provided" // Warn in ../../cmd/s3-gw/app.go
InvalidLifetimeUsingDefaultValue = "invalid lifetime, using default value (in seconds)" // Error in ../../cmd/s3-gw/app_settings.go
InvalidCacheSizeUsingDefaultValue = "invalid cache size, using default value" // Error in ../../cmd/s3-gw/app_settings.go
FailedToParseDefaultLocationConstraint = "failed to parse 'default' location constraint, default one will be used" // Warn in cmd/s3-gw/app_settings.go
FailedToReadRegionMapFilePolicies = "failed to read region map file, policies will be empty" // Warn in cmd/s3-gw/app_settings.go
DefaultLocationConstraintCantBeOverriden = "'default' location constraint can't be overriden by custom policy, use 'placement_policy.default'" // Warn in cmd/s3-gw/app_settings.go
FailedToParseLocationConstraint = "failed to parse location constraint, it cannot be used" // Warn in cmd/s3-gw/app_settings.go
FailedToParseDefaultCopiesNumbers = "failed to parse 'default' copies numbers, default one will be used" // Warn in cmd/s3-gw/app_settings.go
FailedToParseCopiesNumbers = "failed to parse copies numbers, skip" // Warn in cmd/s3-gw/app_settings.go
DefaultNamespacesCannotBeEmpty = "default namespaces cannot be empty, defaults will be used" // Warn in cmd/s3-gw/app_settings.go
FailedToParseNamespacesConfig = "failed to unmarshal namespaces config" // Warn in cmd/s3-gw/app_settings.go
DefaultNamespaceConfigValuesBeOverwritten = "default namespace config value be overwritten by values from 'namespaces.config'" // Warn in cmd/s3-gw/app_settings.go
MultipleDefaultOverridesFound = "multiple default overrides found, only one will be used" // Warn in cmd/s3-gw/app_settings.go
FailedToParseDefaultDefaultLocationConstraint = "failed to parse default 'default' location constraint" // Fatal in cmd/s3-gw/app_settings.go
ConstraintAdded = "constraint added" // Info in ../../cmd/s3-gw/app_settings.go
SkipEmptyAddress = "skip, empty address" // Warn in ../../cmd/s3-gw/app_settings.go
AddedStoragePeer = "added storage peer" // Info in ../../cmd/s3-gw/app_settings.go
PrepareConnectionPool = "prepare connection pool" // Debug in ../../cmd/s3-authmate/modules/utils.go
PrepareFrostfsIDClient = "prepare frostfsid client" // Debug in ../../cmd/s3-authmate/modules/utils.go
PreparePolicyClient = "prepare policy client" // Debug in ../../cmd/s3-authmate/modules/utils.go
CreateSubjectInFrostFSID = "create subject in frostfsid" // Debug in ../../cmd/s3-authmate/modules/utils.go
SubjectAlreadyExistsInFrostFSID = "subject already exists in frostfsid" // Debug in ../../cmd/s3-authmate/modules/utils.go
SetSubjectNameInFrostFSID = "set subject name in frostfsid" // Debug in ../../cmd/s3-authmate/modules/utils.go
AddPolicyChainRules = "add policy chain rules" // Debug in ../../cmd/s3-authmate/modules/utils.go
InvalidCacheEntryType = "invalid cache entry type" // Warn in ../../api/cache/*
InvalidCacheKeyType = "invalid cache key type" // Warn in ../../api/cache/objectslist.go
ObjectIsCopied = "object is copied" // Info in ../../api/handler/copy.go
RequestFailed = "request failed" // Error in ../../api/handler/util.go
GetBucketInfo = "get bucket info" // Warn in ../../api/handler/cors.go
GetBucketCors = "get bucket cors" // Warn in ../../api/handler/cors.go
CouldntDeleteObject = "couldn't delete object" // Error in ../../api/layer/layer.go
BucketIsCreated = "bucket is created" // Info in ../../api/handler/put.go
CouldNotParseContainerObjectLockEnabledAttribute = "could not parse container object lock enabled attribute" // Error in ../../api/layer/container.go
CouldNotListUserContainers = "could not list user containers" // Error in ../../api/layer/container.go
CouldNotFetchContainerInfo = "could not fetch container info" // Error in ../../api/layer/container.go
MismatchedObjEncryptionInfo = "mismatched obj encryptionInfo" // Warn in ../../api/layer/multipart_upload.go
UploadPart = "upload part" // Debug in ../../api/layer/multipart_upload.go
CouldntDeleteOldPartObject = "couldn't delete old part object" // Error in ../../api/layer/multipart_upload.go
CouldNotPutCompletedObject = "could not put a completed object (multipart upload)" // Error in ../../api/layer/multipart_upload.go
CouldNotDeleteUploadPart = "could not delete upload part" // Warn in ../../api/layer/multipart_upload.go
CouldntDeletePart = "couldn't delete part" // Warn in ../../api/layer/multipart_upload.go
PartDetails = "part details" // Debug in ../../api/layer/multipart_upload.go
GetObject = "get object" // Debug in ../../api/layer/layer.go
ResolveBucket = "resolve bucket" // Info in ../../api/layer/layer.go
CouldntDeleteCorsObject = "couldn't delete cors object" // Error in ../../api/layer/cors.go
PutObject = "put object" // Debug in ../../api/layer/object.go
FailedToDeleteObject = "failed to delete object" // Debug in ../../api/layer/object.go
FailedToDiscardPutPayloadProbablyGoroutineLeaks = "failed to discard put payload, probably goroutine leaks" // Warn in ../../api/layer/object.go
FailedToSubmitTaskToPool = "failed to submit task to pool" // Warn in ../../api/layer/object.go
CouldNotFetchObjectMeta = "could not fetch object meta" // Warn in ../../api/layer/object.go
GetTreeNode = "get tree node" // Debug in ../../api/layer/tagging.go
GetTreeNodeToDelete = "get tree node to delete" // Debug in ../../api/layer/tagging.go
CouldntPutBucketInfoIntoCache = "couldn't put bucket info into cache" // Warn in ../../api/layer/cache.go
CouldntAddObjectToCache = "couldn't add object to cache" // Warn in ../../api/layer/cache.go
CouldntCacheAccessControlOperation = "couldn't cache access control operation" // Warn in ../../api/layer/cache.go
CouldntPutObjAddressToNameCache = "couldn't put obj address to name cache" // Warn in ../../api/layer/cache.go
CouldntCacheListOfObjects = "couldn't cache list of objects" // Warn in ../../api/layer/cache.go
CouldntCacheListSession = "couldn't cache list session" // Warn in ../../api/layer/cache.go
CouldntCacheTags = "couldn't cache tags" // Error in ../../api/layer/cache.go
CouldntCacheLockInfo = "couldn't cache lock info" // Error in ../../api/layer/cache.go
CouldntCacheBucketSettings = "couldn't cache bucket settings" // Warn in ../../api/layer/cache.go
CouldntCacheCors = "couldn't cache cors" // Warn in ../../api/layer/cache.go
CouldntCacheListPolicyChains = "couldn't cache list policy chains" // Warn in ../../api/layer/cache.go
RequestEnd = "request end" // Info in ../../api/middleware/response.go
CouldntReceiveAccessBoxForGateKeyRandomKeyWillBeUsed = "couldn't receive access box for gate key, random key will be used" // Debug in ../../api/middleware/auth.go
FailedToPassAuthentication = "failed to pass authentication" // Error in ../../api/middleware/auth.go
FailedToResolveCID = "failed to resolve CID" // Debug in ../../api/middleware/metrics.go
RequestStart = "request start" // Info in ../../api/middleware/reqinfo.go
LogHTTP = "http log" // Info in ../../api/middleware/log_http.go
FailedToCloseHTTPBody = "failed to close http body" // Error in ../../api/middleware/log_http.go
FailedToInitializeHTTPLogger = "failed to initialize http logger" // Error in ../../api/middleware/log_http.go
FailedToReloadHTTPFileLogger = "failed to reload http file logger" // Error in ../../api/middleware/log_http.go
FailedToReadHTTPBody = "failed to read http body" // Error in ../../api/middleware/log_http.go
FailedToProcessHTTPBody = "failed to process http body" // Error in ../../api/middleware/log_http.go
LogHTTPDisabledInThisBuild = "http logging disabled in this build" // Warn in ../../api/middleware/log_http_stub.go
FailedToUnescapeObjectName = "failed to unescape object name" // Warn in ../../api/middleware/reqinfo.go
InvalidDefaultMaxAge = "invalid defaultMaxAge" // Fatal in ../../cmd/s3-gw/app_settings.go
CantShutDownService = "can't shut down service" // Panic in ../../cmd/s3-gw/service.go
CouldntGenerateRandomKey = "couldn't generate random key" // Fatal in ../../cmd/s3-gw/app.go
FailedToCreateResolver = "failed to create resolver" // Fatal in ../../cmd/s3-gw/app.go
CouldNotLoadFrostFSPrivateKey = "could not load FrostFS private key" // Fatal in ../../cmd/s3-gw/app.go
FailedToCreateConnectionPool = "failed to create connection pool" // Fatal in ../../cmd/s3-gw/app.go
FailedToDialConnectionPool = "failed to dial connection pool" // Fatal in ../../cmd/s3-gw/app.go
FailedToCreateTreePool = "failed to create tree pool" // Fatal in ../../cmd/s3-gw/app.go
FailedToDialTreePool = "failed to dial tree pool" // Fatal in ../../cmd/s3-gw/app.go
ListenAndServe = "listen and serve" // Fatal in ../../cmd/s3-gw/app.go
NoHealthyServers = "no healthy servers" // Fatal in ../../cmd/s3-gw/app.go
CouldNotInitializeAPIHandler = "could not initialize API handler" // Fatal in ../../cmd/s3-gw/app.go
RuntimeSoftMemoryDefinedWithGOMEMLIMIT = "soft runtime memory defined with GOMEMLIMIT environment variable, config value skipped" // Warn in ../../cmd/s3-gw/app.go
RuntimeSoftMemoryLimitUpdated = "soft runtime memory limit value updated" // Info in ../../cmd/s3-gw/app.go
AnonRequestSkipFrostfsIDValidation = "anon request, skip FrostfsID validation" // Debug in ../../api/middleware/auth.go
FrostfsIDValidationFailed = "FrostfsID validation failed" // Error in ../../api/middleware/auth.go
InitFrostfsIDContractFailed = "init frostfsid contract failed" // Fatal in ../../cmd/s3-gw/app.go
InitPolicyContractFailed = "init policy contract failed" // Fatal in ../../cmd/s3-gw/app.go
TagFieldName = "tag"
TagApp = "app"
TagDatapath = "datapath"
TagExternalStorage = "external_storage"
TagExternalStorageTree = "external_storage_tree"
TagExternalBlockchain = "external_blockchain"
)
func TagField(tag string) zap.Field {
return zap.String(TagFieldName, tag)
}
// App.
const (
ApplicationStarted = "application started"
ApplicationFinished = "application finished"
StartingServer = "starting server"
StoppingServer = "stopping server"
ServiceIsRunning = "service is running"
ServiceCouldntStartOnConfiguredPort = "service couldn't start on configured port"
ServiceHasntStartedSinceItsDisabled = "service hasn't started since it's disabled"
ShuttingDownService = "shutting down service"
CantGracefullyShutDownService = "can't gracefully shut down service, force stop"
FailedToShutdownTracing = "failed to shutdown tracing"
UsingCredentials = "using credentials"
FailedToAddServer = "failed to add server"
AddServer = "add server"
CantShutDownService = "can't shut down service"
FailedToCreateResolver = "failed to create resolver"
CouldntGenerateRandomKey = "couldn't generate random key"
FailedToCreateConnectionPool = "failed to create connection pool"
FailedToDialConnectionPool = "failed to dial connection pool"
FailedToCreateTreePool = "failed to create tree pool"
FailedToDialTreePool = "failed to dial tree pool"
ListenAndServe = "listen and serve"
NoHealthyServers = "no healthy servers"
CouldNotInitializeAPIHandler = "could not initialize API handler"
InitFrostfsIDFailed = "init frostfsid failed"
InitPolicyContractFailed = "init policy contract failed"
ServerReconnecting = "reconnecting server..."
ServerReconnectedSuccessfully = "server reconnected successfully"
ServerReconnectFailed = "failed to reconnect server"
CouldNotFetchCORSContainerInfo = "couldn't fetch CORS container info"
CouldNotFetchAccessBoxContainerInfo = "couldn't fetch AccessBox container info"
MultinetDialSuccess = "multinet dial successful"
MultinetDialFail = "multinet dial failed"
FailedToCreateWorkerPool = "failed to create worker pool"
CouldNotLoadFrostFSPrivateKey = "could not load FrostFS private key"
AddedStoragePeer = "added storage peer"
SIGHUPConfigReloadStarted = "SIGHUP config reload started"
MetricsAreDisabled = "metrics are disabled"
ConstraintAdded = "constraint added"
ContainerResolverWillBeDisabled = "container resolver will be disabled because of resolvers 'resolver_order' is empty"
FailedToReloadConfigBecauseItsMissed = "failed to reload config because it's missed"
FailedToReloadConfig = "failed to reload config"
TracingConfigUpdated = "tracing config updated"
FailedToReloadResolvers = "failed to reload resolvers"
FailedToReloadServerParameters = "failed to reload server parameters"
SIGHUPConfigReloadCompleted = "SIGHUP config reload completed"
LogLevelWontBeUpdated = "log level won't be updated"
ResolverNNSWontBeUsedSinceRPCEndpointIsntProvided = "resolver 'nns' won't be used since 'rpc_endpoint' isn't provided"
InvalidLifetimeUsingDefaultValue = "invalid lifetime, using default value (in seconds)"
InvalidCacheSizeUsingDefaultValue = "invalid cache size, using default value"
FailedToParseDefaultLocationConstraint = "failed to parse 'default' location constraint, default one will be used"
FailedToReadRegionMapFilePolicies = "failed to read region map file, policies will be empty"
DefaultLocationConstraintCantBeOverriden = "'default' location constraint can't be overriden by custom policy, use 'placement_policy.default'"
FailedToParseLocationConstraint = "failed to parse location constraint, it cannot be used"
FailedToParseDefaultCopiesNumbers = "failed to parse 'default' copies numbers, default one will be used"
FailedToParseCopiesNumbers = "failed to parse copies numbers, skip"
FailedToInitializeTracing = "failed to initialize tracing"
DefaultNamespacesCannotBeEmpty = "default namespaces cannot be empty, defaults will be used"
FailedToParseNamespacesConfig = "failed to unmarshal namespaces config"
DefaultNamespaceConfigValuesBeOverwritten = "default namespace config value be overwritten by values from 'namespaces.config'"
MultipleDefaultOverridesFound = "multiple default overrides found, only one will be used"
SkipEmptyAddress = "skip, empty address"
FailedToParseDefaultDefaultLocationConstraint = "failed to parse default 'default' location constraint"
LogHTTPDisabledInThisBuild = "http logging disabled in this build"
InvalidDefaultMaxAge = "invalid defaultMaxAge"
RuntimeSoftMemoryDefinedWithGOMEMLIMIT = "soft runtime memory defined with GOMEMLIMIT environment variable, config value skipped"
RuntimeSoftMemoryLimitUpdated = "soft runtime memory limit value updated"
InvalidAccessBoxCacheRemovingCheckInterval = "invalid accessbox check removing interval, using default value"
WarnDuplicateAddress = "duplicate address"
FailedToLoadMultinetConfig = "failed to load multinet config"
MultinetConfigWontBeUpdated = "multinet config won't be updated"
WarnDomainContainsPort = "the domain contains a port, domain skipped"
TagsLogConfigWontBeUpdated = "tags log config won't be updated"
CouldNotFetchLifecycleContainerInfo = "couldn't fetch lifecycle container info"
WarnDuplicateNamespaceVHS = "duplicate namespace with enabled VHS, config value skipped"
WarnValueVHSEnabledFlagWrongType = "the value of the VHS enable flag for the namespace is of the wrong type, config value skipped"
WarnDomainContainsInvalidPlaceholder = "the domain contains an invalid placeholder, domain skipped"
)
// Datapath.
const (
NotSupported = "not supported"
RequestUnmatched = "request unmatched"
RequestStart = "request start"
RequestFailed = "request failed"
RequestEnd = "request end"
AnonRequestSkipFrostfsIDValidation = "anon request, skip FrostfsID validation"
CouldntReceiveAccessBoxForGateKeyRandomKeyWillBeUsed = "couldn't receive access box for gate key, random key will be used"
FrostfsIDValidationFailed = "FrostfsID validation failed"
FailedToPassAuthentication = "failed to pass authentication"
PolicyValidationFailed = "policy validation failed"
ServerReconnecting = "reconnecting server..."
ServerReconnectedSuccessfully = "server reconnected successfully"
ServerReconnectFailed = "failed to reconnect server"
ParseTreeNode = "parse tree node"
FailedToGetRealObjectSize = "failed to get real object size"
CouldntDeleteObjectFromStorageContinueDeleting = "couldn't delete object from storage, continue deleting from tree"
CouldntPutAccessBoxIntoCache = "couldn't put accessbox into cache"
InvalidAccessBoxCacheRemovingCheckInterval = "invalid accessbox check removing interval, using default value"
CouldNotCloseRequestBody = "could not close request body"
BucketOwnerKeyIsMissing = "bucket owner key is missing"
SettingsNodeInvalidOwnerKey = "settings node: invalid owner key"
SuccessfulAuth = "successful auth"
PolicyRequest = "policy request"
FailedToGenerateRequestID = "failed to generate request id"
InvalidBucketObjectLockEnabledHeader = "invalid X-Amz-Bucket-Object-Lock-Enabled header"
InvalidTreeKV = "invalid tree service meta KV"
FailedToWriteResponse = "failed to write response"
WarnDuplicateAddress = "duplicate address"
PolicyCouldntBeConvertedToNativeRules = "policy couldn't be converted to native rules, only s3 rules be applied"
CouldntCacheSubject = "couldn't cache subject info"
UserGroupsListIsEmpty = "user groups list is empty, subject not found"
CouldntCacheUserKey = "couldn't cache user key"
ObjectTaggingNodeHasMultipleIDs = "object tagging node has multiple ids"
BucketTaggingNodeHasMultipleIDs = "bucket tagging node has multiple ids"
BucketSettingsNodeHasMultipleIDs = "bucket settings node has multiple ids"
BucketCORSNodeHasMultipleIDs = "bucket cors node has multiple ids"
SystemNodeHasMultipleIDs = "system node has multiple ids"
FailedToRemoveOldSystemNode = "failed to remove old system node"
FailedToParseHTTPTime = "failed to parse http time, header is ignored"
WarnInvalidTypeTLSTerminationHeader = "invalid type of value of tls termination header"
FailedToUnescapeObjectName = "failed to unescape object name"
MismatchedObjEncryptionInfo = "mismatched obj encryptionInfo"
CouldNotParseContainerObjectLockEnabledAttribute = "could not parse container object lock enabled attribute"
FailedToParsePartInfo = "failed to parse part info"
SettingsNodeInvalidOwnerKey = "settings node: invalid owner key"
FailedToParseAddressInTreeNode = "failed to parse object addr in tree node"
UnexpectedMultiNodeIDsInSubTreeMultiParts = "unexpected multi node ids in sub tree multi parts"
FoundSeveralSystemNodes = "found several system nodes"
FailedToParsePartInfo = "failed to parse part info"
CouldNotFetchCORSContainerInfo = "couldn't fetch CORS container info"
CouldNotFetchAccessBoxContainerInfo = "couldn't fetch AccessBox container info"
CloseCredsObjectPayload = "close creds object payload"
CouldntDeleteLifecycleObject = "couldn't delete lifecycle configuration object"
CouldntCacheLifecycleConfiguration = "couldn't cache lifecycle configuration"
CouldNotFetchLifecycleContainerInfo = "couldn't fetch lifecycle container info"
BucketLifecycleNodeHasMultipleIDs = "bucket lifecycle node has multiple ids"
GetBucketLifecycle = "get bucket lifecycle"
WarnDuplicateNamespaceVHS = "duplicate namespace with enabled VHS, config value skipped"
WarnValueVHSEnabledFlagWrongType = "the value of the VHS enable flag for the namespace is of the wrong type, config value skipped"
WarnDomainContainsInvalidPlaceholder = "the domain contains an invalid placeholder, domain skipped"
FailedToRemoveOldPartNode = "failed to remove old part node"
CouldntCacheNetworkInfo = "couldn't cache network info"
NotSupported = "not supported"
CheckCustomAccessKeyIDUniqueness = "check custom access key id uniqueness"
FailedToLoadMultinetConfig = "failed to load multinet config"
MultinetConfigWontBeUpdated = "multinet config won't be updated"
MultinetDialSuccess = "multinet dial successful"
MultinetDialFail = "multinet dial failed"
FailedToParseHTTPTime = "failed to parse http time, header is ignored"
FailedToPutTombstoneObject = "failed to put tombstone object"
FailedToCreateWorkerPool = "failed to create worker pool"
FailedToListAllObjectRelations = "failed to list all object relations"
WarnInvalidTypeTLSTerminationHeader = "invalid type of value of tls termination header"
FailedToPutTombstones = "failed to put tombstones"
WarnDomainContainsPort = "the domain contains a port, domain skipped"
CouldntCacheNetmap = "couldn't cache netmap"
UploadPart = "upload part"
FailedToSubmitTaskToPool = "failed to submit task to pool"
FailedToGetRealObjectSize = "failed to get real object size"
PartDetails = "part details"
BucketSettingsNotFoundUseDefaults = "bucket settings not found, use defaults"
ParseTreeNode = "parse tree node"
GetObject = "get object"
GetTreeNodeToDelete = "get tree node to delete"
InvalidBucketObjectLockEnabledHeader = "invalid X-Amz-Bucket-Object-Lock-Enabled header"
InvalidCacheEntryType = "invalid cache entry type"
InvalidCacheKeyType = "invalid cache key type"
CouldntPutBucketInfoIntoCache = "couldn't put bucket info into cache"
CouldntAddObjectToCache = "couldn't add object to cache"
CouldntCacheAccessControlOperation = "couldn't cache access control operation"
CouldntPutObjAddressToNameCache = "couldn't put obj address to name cache"
CouldntPutAccessBoxIntoCache = "couldn't put accessbox into cache"
CouldntCacheListOfObjects = "couldn't cache list of objects"
CouldntCacheListSession = "couldn't cache list session"
CouldntCacheTags = "couldn't cache tags"
CouldntCacheLockInfo = "couldn't cache lock info"
CouldntCacheNetworkInfo = "couldn't cache network info"
CouldntCacheSubject = "couldn't cache subject info"
CouldntCacheNetmap = "couldn't cache netmap"
CouldntCacheUserKey = "couldn't cache user key"
CouldntCacheBucketSettings = "couldn't cache bucket settings"
CouldntCacheCors = "couldn't cache cors"
CouldntCacheListPolicyChains = "couldn't cache list policy chains"
CouldntCacheLifecycleConfiguration = "couldn't cache lifecycle configuration"
GetBucketCors = "get bucket cors"
GetBucketInfo = "get bucket info"
ResolveBucket = "resolve bucket"
FailedToResolveCID = "failed to resolve CID"
FailedToDiscardPutPayloadProbablyGoroutineLeaks = "failed to discard put payload, probably goroutine leaks"
)
// External storage.
const (
ObjectIsCopied = "object is copied"
CouldntDeleteObject = "couldn't delete object"
CouldNotListUserContainers = "could not list user containers"
CouldNotFetchContainerInfo = "could not fetch container info"
CouldntDeleteObjectFromStorageContinueDeleting = "couldn't delete object from storage, continue deleting from tree"
FailedToPutTombstoneObject = "failed to put tombstone object"
FailedToListAllObjectRelations = "failed to list all object relations"
FailedToPutTombstones = "failed to put tombstones"
CouldntDeleteCorsObject = "couldn't delete cors object"
BucketIsCreated = "bucket is created"
CouldntDeleteOldPartObject = "couldn't delete old part object"
CouldNotPutCompletedObject = "could not put a completed object (multipart upload)"
CouldNotDeleteUploadPart = "could not delete upload part"
PutObject = "put object"
CouldNotFetchObjectMeta = "could not fetch object meta"
FailedToDeleteObject = "failed to delete object"
CouldntDeleteLifecycleObject = "couldn't delete lifecycle configuration object"
CouldntGetCORSObjectVersions = "couldn't get cors object versions"
)
// External blockchain.
const (
UserGroupsListIsEmpty = "user groups list is empty, subject not found"
)
// External storage tree.
const (
FoundMoreThanOneUnversionedNode = "found more than one unversioned node"
GetTreeNode = "get tree node"
InvalidTreeKV = "invalid tree service meta KV"
FailedToRemoveOldSystemNode = "failed to remove old system node"
GetBucketLifecycle = "get bucket lifecycle"
FailedToRemoveOldPartNode = "failed to remove old part node"
SystemNodeHasMultipleIDs = "system node has multiple ids"
ObjectTaggingNodeHasMultipleIDs = "object tagging node has multiple ids"
BucketTaggingNodeHasMultipleIDs = "bucket tagging node has multiple ids"
BucketSettingsNodeHasMultipleIDs = "bucket settings node has multiple ids"
GetAllBucketCorsFromTree = "get all bucket cors from tree"
CouldntDeleteBucketCORS = "couldn't delete bucket cors"
)
// Authmate.
const (
CreateContainer = "create container"
CheckContainer = "check container"
CheckCustomAccessKeyIDUniqueness = "check custom access key id uniqueness"
StoreBearerTokenIntoFrostFS = "store bearer token into FrostFS"
UpdateAccessCredObjectIntoFrostFS = "update access cred object into FrostFS"
SubjectAlreadyExistsInFrostFSID = "subject already exists in frostfsid"
SetSubjectNameInFrostFSID = "set subject name in frostfsid"
AddPolicyChainRules = "add policy chain rules"
PrepareConnectionPool = "prepare connection pool"
PrepareFrostfsIDClient = "prepare frostfsid client"
PreparePolicyClient = "prepare policy client"
CreateSubjectInFrostFSID = "create subject in frostfsid"
CloseCredsObjectPayload = "close creds object payload"
)
// Log HTTP.
const (
LogHTTP = "http log"
FailedToCloseHTTPBody = "failed to close http body"
FailedToInitializeHTTPLogger = "failed to initialize http logger"
FailedToReadHTTPBody = "failed to read http body"
FailedToProcessHTTPBody = "failed to process http body"
)

View file

@ -17,9 +17,16 @@ func (l LogEventHandler) DialPerformed(sourceIP net.Addr, _, address string, err
sourceIPString = sourceIP.Network() + "://" + sourceIP.String()
}
if err == nil {
l.logger.Debug(logs.MultinetDialSuccess, zap.String("source", sourceIPString), zap.String("destination", address))
l.logger.Debug(logs.MultinetDialSuccess,
zap.String("source", sourceIPString),
zap.String("destination", address),
logs.TagField(logs.TagApp))
} else {
l.logger.Debug(logs.MultinetDialFail, zap.String("source", sourceIPString), zap.String("destination", address), zap.Error(err))
l.logger.Debug(logs.MultinetDialFail,
zap.String("source", sourceIPString),
zap.String("destination", address),
zap.Error(err),
logs.TagField(logs.TagApp))
}
}

View file

@ -27,7 +27,7 @@ type AppMetricsConfig struct {
func NewAppMetrics(cfg AppMetricsConfig) *AppMetrics {
if !cfg.Enabled {
cfg.Logger.Warn(logs.MetricsAreDisabled)
cfg.Logger.Warn(logs.MetricsAreDisabled, logs.TagField(logs.TagApp))
}
registry := cfg.Registerer
@ -44,7 +44,7 @@ func NewAppMetrics(cfg AppMetricsConfig) *AppMetrics {
func (m *AppMetrics) SetEnabled(enabled bool) {
if !enabled {
m.logger.Warn(logs.MetricsAreDisabled)
m.logger.Warn(logs.MetricsAreDisabled, logs.TagField(logs.TagApp))
}
m.mu.Lock()

View file

@ -11,6 +11,7 @@ import (
"strings"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/tree"
@ -257,7 +258,7 @@ func newNodeVersionFromTreeNode(log *zap.Logger, filePath string, treeNode *tree
if createdStr, ok := treeNode.Get(createdKV); ok {
if utcMilli, err := strconv.ParseInt(createdStr, 10, 64); err != nil {
log.Warn(logs.InvalidTreeKV, zap.String(createdKV, createdStr), zap.Error(err))
log.Warn(logs.InvalidTreeKV, zap.String(createdKV, createdStr), zap.Error(err), logs.TagField(logs.TagExternalStorageTree))
} else {
created := time.UnixMilli(utcMilli)
version.Created = &created
@ -267,7 +268,7 @@ func newNodeVersionFromTreeNode(log *zap.Logger, filePath string, treeNode *tree
if ownerStr, ok := treeNode.Get(ownerKV); ok {
var owner user.ID
if err := owner.DecodeString(ownerStr); err != nil {
log.Warn(logs.InvalidTreeKV, zap.String(ownerKV, ownerStr), zap.Error(err))
log.Warn(logs.InvalidTreeKV, zap.String(ownerKV, ownerStr), zap.Error(err), logs.TagField(logs.TagExternalStorageTree))
} else {
version.Owner = &owner
}
@ -275,7 +276,7 @@ func newNodeVersionFromTreeNode(log *zap.Logger, filePath string, treeNode *tree
if creationEpoch, ok := treeNode.Get(creationEpochKV); ok {
if epoch, err := strconv.ParseUint(creationEpoch, 10, 64); err != nil {
log.Warn(logs.InvalidTreeKV, zap.String(creationEpochKV, creationEpoch), zap.Error(err))
log.Warn(logs.InvalidTreeKV, zap.String(creationEpochKV, creationEpoch), zap.Error(err), logs.TagField(logs.TagExternalStorageTree))
} else {
version.CreationEpoch = epoch
}
@ -342,13 +343,13 @@ func newMultipartInfoFromTreeNode(log *zap.Logger, filePath string, treeNode *tr
if ownerID, ok := treeNode.Get(ownerKV); ok {
if err := multipartInfo.Owner.DecodeString(ownerID); err != nil {
log.Warn(logs.InvalidTreeKV, zap.String(ownerKV, ownerID), zap.Error(err))
log.Warn(logs.InvalidTreeKV, zap.String(ownerKV, ownerID), zap.Error(err), logs.TagField(logs.TagExternalStorageTree))
}
}
if created, ok := treeNode.Get(createdKV); ok {
if utcMilli, err := strconv.ParseInt(created, 10, 64); err != nil {
log.Warn(logs.InvalidTreeKV, zap.String(createdKV, created), zap.Error(err))
log.Warn(logs.InvalidTreeKV, zap.String(createdKV, created), zap.Error(err), logs.TagField(logs.TagExternalStorageTree))
} else {
multipartInfo.Created = time.UnixMilli(utcMilli)
}
@ -356,7 +357,7 @@ func newMultipartInfoFromTreeNode(log *zap.Logger, filePath string, treeNode *tr
if finished, ok := treeNode.Get(finishedKV); ok {
if flag, err := strconv.ParseBool(finished); err != nil {
log.Warn(logs.InvalidTreeKV, zap.String(finishedKV, finished), zap.Error(err))
log.Warn(logs.InvalidTreeKV, zap.String(finishedKV, finished), zap.Error(err), logs.TagField(logs.TagExternalStorageTree))
} else {
multipartInfo.Finished = flag
}
@ -364,7 +365,7 @@ func newMultipartInfoFromTreeNode(log *zap.Logger, filePath string, treeNode *tr
if creationEpoch, ok := treeNode.Get(creationEpochKV); ok {
if epoch, err := strconv.ParseUint(creationEpoch, 10, 64); err != nil {
log.Warn(logs.InvalidTreeKV, zap.String(creationEpochKV, creationEpoch), zap.Error(err))
log.Warn(logs.InvalidTreeKV, zap.String(creationEpochKV, creationEpoch), zap.Error(err), logs.TagField(logs.TagExternalStorageTree))
} else {
multipartInfo.CreationEpoch = epoch
}
@ -395,23 +396,23 @@ func newMultipartInfo(log *zap.Logger, node NodeResponse) (*data.MultipartInfo,
multipartInfo.Key = string(kv.GetValue())
case createdKV:
if utcMilli, err := strconv.ParseInt(string(kv.GetValue()), 10, 64); err != nil {
log.Warn(logs.InvalidTreeKV, zap.String(createdKV, string(kv.GetValue())), zap.Error(err))
log.Warn(logs.InvalidTreeKV, zap.String(createdKV, string(kv.GetValue())), zap.Error(err), logs.TagField(logs.TagExternalStorageTree))
} else {
multipartInfo.Created = time.UnixMilli(utcMilli)
}
case ownerKV:
if err := multipartInfo.Owner.DecodeString(string(kv.GetValue())); err != nil {
log.Warn(logs.InvalidTreeKV, zap.String(ownerKV, string(kv.GetValue())), zap.Error(err))
log.Warn(logs.InvalidTreeKV, zap.String(ownerKV, string(kv.GetValue())), zap.Error(err), logs.TagField(logs.TagExternalStorageTree))
}
case finishedKV:
if isFinished, err := strconv.ParseBool(string(kv.GetValue())); err != nil {
log.Warn(logs.InvalidTreeKV, zap.String(finishedKV, string(kv.GetValue())), zap.Error(err))
log.Warn(logs.InvalidTreeKV, zap.String(finishedKV, string(kv.GetValue())), zap.Error(err), logs.TagField(logs.TagExternalStorageTree))
} else {
multipartInfo.Finished = isFinished
}
case creationEpochKV:
if epoch, err := strconv.ParseUint(string(kv.GetValue()), 10, 64); err != nil {
log.Warn(logs.InvalidTreeKV, zap.String(creationEpochKV, string(kv.GetValue())), zap.Error(err))
log.Warn(logs.InvalidTreeKV, zap.String(creationEpochKV, string(kv.GetValue())), zap.Error(err), logs.TagField(logs.TagExternalStorageTree))
} else {
multipartInfo.CreationEpoch = epoch
}
@ -493,6 +494,9 @@ func newPartInfo(node NodeResponse) (*data.PartInfoExtended, error) {
}
func (c *Tree) GetSettingsNode(ctx context.Context, bktInfo *data.BucketInfo) (*data.BucketSettings, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.GetSettingsNode")
defer span.End()
multiNode, err := c.getSystemNode(ctx, bktInfo, settingsFileName)
if err != nil {
return nil, fmt.Errorf("couldn't get node: %w", err)
@ -515,7 +519,7 @@ func (c *Tree) GetSettingsNode(ctx context.Context, bktInfo *data.BucketInfo) (*
if ownerKeyHex, ok := node.Get(ownerKeyKV); ok {
if settings.OwnerKey, err = keys.NewPublicKeyFromString(ownerKeyHex); err != nil {
c.reqLogger(ctx).Error(logs.SettingsNodeInvalidOwnerKey, zap.Error(err))
c.reqLogger(ctx).Error(logs.SettingsNodeInvalidOwnerKey, zap.Error(err), logs.TagField(logs.TagDatapath))
}
}
@ -523,6 +527,9 @@ func (c *Tree) GetSettingsNode(ctx context.Context, bktInfo *data.BucketInfo) (*
}
func (c *Tree) PutSettingsNode(ctx context.Context, bktInfo *data.BucketInfo, settings *data.BucketSettings) error {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.PutSettingsNode")
defer span.End()
multiNode, err := c.getSystemNode(ctx, bktInfo, settingsFileName)
isErrNotFound := errors.Is(err, tree.ErrNodeNotFound)
if err != nil && !isErrNotFound {
@ -539,7 +546,7 @@ func (c *Tree) PutSettingsNode(ctx context.Context, bktInfo *data.BucketInfo, se
latest := multiNode.Latest()
ind := latest.GetLatestNodeIndex()
if latest.IsSplit() {
c.reqLogger(ctx).Error(logs.BucketSettingsNodeHasMultipleIDs, zap.Uint64s("ids", latest.ID))
c.reqLogger(ctx).Error(logs.BucketSettingsNodeHasMultipleIDs, zap.Uint64s("ids", latest.ID), logs.TagField(logs.TagExternalStorageTree))
}
if err = c.service.MoveNode(ctx, bktInfo, systemTree, latest.ID[ind], 0, meta); err != nil {
@ -552,6 +559,9 @@ func (c *Tree) PutSettingsNode(ctx context.Context, bktInfo *data.BucketInfo, se
}
func (c *Tree) GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.Address, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.GetBucketCORS")
defer span.End()
node, err := c.getSystemNode(ctx, bktInfo, corsFilename)
if err != nil {
return oid.Address{}, err
@ -560,47 +570,31 @@ func (c *Tree) GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid
return getTreeNodeAddress(node.Latest())
}
func (c *Tree) PutBucketCORS(ctx context.Context, bktInfo *data.BucketInfo, addr oid.Address) ([]oid.Address, error) {
multiNode, err := c.getSystemNode(ctx, bktInfo, corsFilename)
isErrNotFound := errors.Is(err, tree.ErrNodeNotFound)
if err != nil && !isErrNotFound {
return nil, fmt.Errorf("couldn't get node: %w", err)
func (c *Tree) GetAllBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) ([]oid.Address, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.GetAllBucketCORS")
defer span.End()
node, err := c.getSystemNode(ctx, bktInfo, corsFilename)
if err != nil {
return nil, err
}
meta := make(map[string]string)
meta[FileNameKey] = corsFilename
meta[oidKV] = addr.Object().EncodeToString()
meta[cidKV] = addr.Container().EncodeToString()
if isErrNotFound {
if _, err = c.service.AddNode(ctx, bktInfo, systemTree, 0, meta); err != nil {
addrs := make([]oid.Address, 0, len(node.nodes))
for _, corsNode := range node.nodes {
addr, err := getTreeNodeAddress(corsNode)
if err != nil {
return nil, err
}
return nil, tree.ErrNoNodeToRemove
addrs = append(addrs, addr)
}
latest := multiNode.Latest()
ind := latest.GetLatestNodeIndex()
if latest.IsSplit() {
c.reqLogger(ctx).Error(logs.BucketCORSNodeHasMultipleIDs)
}
if err = c.service.MoveNode(ctx, bktInfo, systemTree, latest.ID[ind], 0, meta); err != nil {
return nil, fmt.Errorf("move cors node: %w", err)
}
objToDelete := make([]oid.Address, 1, len(multiNode.nodes))
objToDelete[0], err = getTreeNodeAddress(latest)
if err != nil {
return nil, fmt.Errorf("parse object addr of latest cors node in tree: %w", err)
}
objToDelete = append(objToDelete, c.cleanOldNodes(ctx, multiNode.Old(), bktInfo)...)
return objToDelete, nil
return addrs, nil
}
func (c *Tree) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) ([]oid.Address, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.DeleteBucketCORS")
defer span.End()
multiNode, err := c.getSystemNode(ctx, bktInfo, corsFilename)
isErrNotFound := errors.Is(err, tree.ErrNodeNotFound)
if err != nil && !isErrNotFound {
@ -640,14 +634,14 @@ func (c *Tree) cleanOldNodes(ctx context.Context, nodes []*treeNode, bktInfo *da
for _, node := range nodes {
ind := node.GetLatestNodeIndex()
if node.IsSplit() {
c.reqLogger(ctx).Error(logs.SystemNodeHasMultipleIDs, zap.String("FileName", node.Meta[FileNameKey]), zap.Uint64s("ids", node.ID))
c.reqLogger(ctx).Error(logs.SystemNodeHasMultipleIDs, zap.String("FileName", node.Meta[FileNameKey]), zap.Uint64s("ids", node.ID), logs.TagField(logs.TagExternalStorageTree))
}
if err := c.service.RemoveNode(ctx, bktInfo, systemTree, node.ID[ind]); err != nil {
c.reqLogger(ctx).Warn(logs.FailedToRemoveOldSystemNode, zap.String("FileName", node.Meta[FileNameKey]), zap.Uint64("id", node.ID[ind]))
c.reqLogger(ctx).Warn(logs.FailedToRemoveOldSystemNode, zap.String("FileName", node.Meta[FileNameKey]), zap.Uint64("id", node.ID[ind]), logs.TagField(logs.TagExternalStorageTree))
} else {
addr, err := getTreeNodeAddress(node)
if err != nil {
c.log.Warn(logs.FailedToParseAddressInTreeNode, zap.String("FileName", node.Meta[FileNameKey]), zap.Uint64("id", node.ID[ind]))
c.log.Warn(logs.FailedToParseAddressInTreeNode, zap.String("FileName", node.Meta[FileNameKey]), zap.Uint64("id", node.ID[ind]), logs.TagField(logs.TagDatapath))
continue
}
res = append(res, addr)
@ -658,6 +652,9 @@ func (c *Tree) cleanOldNodes(ctx context.Context, nodes []*treeNode, bktInfo *da
}
func (c *Tree) GetObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion) (map[string]string, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.GetObjectTagging")
defer span.End()
tagNode, err := c.getTreeNode(ctx, bktInfo, objVersion.ID, isTagKV)
if err != nil {
return nil, err
@ -683,6 +680,9 @@ func getObjectTagging(tagNode *treeNode) map[string]string {
}
func (c *Tree) PutObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion, tagSet map[string]string) error {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.PutObjectTagging")
defer span.End()
tagNode, err := c.getTreeNode(ctx, bktInfo, objVersion.ID, isTagKV)
if err != nil {
return err
@ -702,17 +702,23 @@ func (c *Tree) PutObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, o
ind := tagNode.GetLatestNodeIndex()
if tagNode.IsSplit() {
c.reqLogger(ctx).Error(logs.ObjectTaggingNodeHasMultipleIDs)
c.reqLogger(ctx).Error(logs.ObjectTaggingNodeHasMultipleIDs, logs.TagField(logs.TagExternalStorageTree))
}
return c.service.MoveNode(ctx, bktInfo, versionTree, tagNode.ID[ind], objVersion.ID, treeTagSet)
}
func (c *Tree) DeleteObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion) error {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.DeleteObjectTagging")
defer span.End()
return c.PutObjectTagging(ctx, bktInfo, objVersion, nil)
}
func (c *Tree) GetBucketTagging(ctx context.Context, bktInfo *data.BucketInfo) (map[string]string, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.GetBucketTagging")
defer span.End()
multiNode, err := c.getSystemNode(ctx, bktInfo, bucketTaggingFilename)
if err != nil {
return nil, err
@ -730,6 +736,9 @@ func (c *Tree) GetBucketTagging(ctx context.Context, bktInfo *data.BucketInfo) (
}
func (c *Tree) PutBucketTagging(ctx context.Context, bktInfo *data.BucketInfo, tagSet map[string]string) error {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.PutBucketTagging")
defer span.End()
multiNode, err := c.getSystemNode(ctx, bktInfo, bucketTaggingFilename)
isErrNotFound := errors.Is(err, tree.ErrNodeNotFound)
if err != nil && !isErrNotFound {
@ -751,7 +760,7 @@ func (c *Tree) PutBucketTagging(ctx context.Context, bktInfo *data.BucketInfo, t
latest := multiNode.Latest()
ind := latest.GetLatestNodeIndex()
if latest.IsSplit() {
c.reqLogger(ctx).Error(logs.BucketTaggingNodeHasMultipleIDs, zap.Uint64s("ids", latest.ID))
c.reqLogger(ctx).Error(logs.BucketTaggingNodeHasMultipleIDs, zap.Uint64s("ids", latest.ID), logs.TagField(logs.TagExternalStorageTree))
}
if err = c.service.MoveNode(ctx, bktInfo, systemTree, latest.ID[ind], 0, treeTagSet); err != nil {
@ -764,6 +773,9 @@ func (c *Tree) PutBucketTagging(ctx context.Context, bktInfo *data.BucketInfo, t
}
func (c *Tree) DeleteBucketTagging(ctx context.Context, bktInfo *data.BucketInfo) error {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.DeleteBucketTagging")
defer span.End()
return c.PutBucketTagging(ctx, bktInfo, nil)
}
@ -807,10 +819,16 @@ func (c *Tree) getTreeNodes(ctx context.Context, bktInfo *data.BucketInfo, nodeI
}
func (c *Tree) GetVersions(ctx context.Context, bktInfo *data.BucketInfo, filepath string) ([]*data.NodeVersion, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.GetVersions")
defer span.End()
return c.getVersions(ctx, bktInfo, versionTree, filepath, false)
}
func (c *Tree) GetLatestVersion(ctx context.Context, bktInfo *data.BucketInfo, objectName string) (*data.NodeVersion, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.GetLatestVersion")
defer span.End()
meta := []string{oidKV, isCombinedKV, isUnversionedKV, isDeleteMarkerKV, etagKV, sizeKV, md5KV, creationEpochKV}
path := pathFromName(objectName)
@ -1049,7 +1067,7 @@ func (s *VersionsByPrefixStreamImpl) parseNodeResponse(node NodeResponse) (res *
trNode, fileName, err := parseTreeNode(node)
if err != nil {
if !errors.Is(err, errNodeDoesntContainFileName) {
s.log.Debug(logs.ParseTreeNode, zap.Error(err))
s.log.Debug(logs.ParseTreeNode, zap.Error(err), logs.TagField(logs.TagDatapath))
}
return nil, true, nil
}
@ -1080,6 +1098,9 @@ func (s *VersionsByPrefixStreamImpl) parseNodeResponse(node NodeResponse) (res *
}
func (c *Tree) InitVersionsByPrefixStream(ctx context.Context, bktInfo *data.BucketInfo, prefix string, latestOnly bool) (data.VersionsStream, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.InitVersionsByPrefixStream")
defer span.End()
mainStream, tailPrefix, rootID, err := c.getSubTreeByPrefixMainStream(ctx, bktInfo, versionTree, prefix)
if err != nil {
if errors.Is(err, io.EOF) {
@ -1272,6 +1293,9 @@ func formLatestNodeKey(parentID uint64, fileName string) string {
}
func (c *Tree) GetUnversioned(ctx context.Context, bktInfo *data.BucketInfo, filepath string) (*data.NodeVersion, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.GetUnversioned")
defer span.End()
return c.getUnversioned(ctx, bktInfo, versionTree, filepath)
}
@ -1287,7 +1311,9 @@ func (c *Tree) getUnversioned(ctx context.Context, bktInfo *data.BucketInfo, tre
if len(nodes) > 1 {
c.reqLogger(ctx).Debug(logs.FoundMoreThanOneUnversionedNode,
zap.String("treeID", treeID), zap.String("filepath", filepath))
zap.String("treeID", treeID),
zap.String("filepath", filepath),
logs.TagField(logs.TagExternalStorageTree))
}
sort.Slice(nodes, func(i, j int) bool {
@ -1298,14 +1324,23 @@ func (c *Tree) getUnversioned(ctx context.Context, bktInfo *data.BucketInfo, tre
}
func (c *Tree) AddVersion(ctx context.Context, bktInfo *data.BucketInfo, version *data.NodeVersion) (uint64, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.AddVersion")
defer span.End()
return c.addVersion(ctx, bktInfo, versionTree, version)
}
func (c *Tree) RemoveVersion(ctx context.Context, bktInfo *data.BucketInfo, id uint64) error {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.RemoveVersion")
defer span.End()
return c.service.RemoveNode(ctx, bktInfo, versionTree, id)
}
func (c *Tree) CreateMultipartUpload(ctx context.Context, bktInfo *data.BucketInfo, info *data.MultipartInfo) error {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.CreateMultipartUpload")
defer span.End()
path := pathFromName(info.Key)
meta := metaFromMultipart(info, path[len(path)-1])
_, err := c.service.AddNodeByPath(ctx, bktInfo, systemTree, path[:len(path)-1], meta)
@ -1314,6 +1349,9 @@ func (c *Tree) CreateMultipartUpload(ctx context.Context, bktInfo *data.BucketIn
}
func (c *Tree) GetMultipartUploadsByPrefix(ctx context.Context, bktInfo *data.BucketInfo, prefix string) ([]*data.MultipartInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.GetMultipartUploadsByPrefix")
defer span.End()
subTreeNodes, headPrefix, err := c.getSubTreeByPrefix(ctx, bktInfo, systemTree, prefix, false)
if err != nil {
return nil, err
@ -1394,6 +1432,9 @@ func (c *Tree) getSubTreeMultipartUploads(ctx context.Context, bktInfo *data.Buc
}
func (c *Tree) GetMultipartUpload(ctx context.Context, bktInfo *data.BucketInfo, objectName, uploadID string) (*data.MultipartInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.GetMultipartUpload")
defer span.End()
path := pathFromName(objectName)
p := &GetNodesParams{
BktInfo: bktInfo,
@ -1425,6 +1466,9 @@ func (c *Tree) GetMultipartUpload(ctx context.Context, bktInfo *data.BucketInfo,
}
func (c *Tree) AddPart(ctx context.Context, bktInfo *data.BucketInfo, multipartNodeID uint64, info *data.PartInfo) (oldObjIDsToDelete []oid.ID, err error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.AddPart")
defer span.End()
parts, err := c.service.GetSubTree(ctx, bktInfo, systemTree, []uint64{multipartNodeID}, 2, false)
if err != nil {
return nil, err
@ -1460,7 +1504,8 @@ func (c *Tree) AddPart(ctx context.Context, bktInfo *data.BucketInfo, multipartN
zap.String("upload id", info.UploadID),
zap.Uint64("multipart node id ", multipartNodeID),
zap.Uint64s("id", part.GetNodeID()),
zap.Error(err))
zap.Error(err),
logs.TagField(logs.TagDatapath))
continue
}
if partInfo.Number == info.Number {
@ -1488,7 +1533,8 @@ func (c *Tree) AddPart(ctx context.Context, bktInfo *data.BucketInfo, multipartN
c.reqLogger(ctx).Warn(logs.FailedToRemoveOldPartNode,
zap.String("key", info.Key),
zap.String("upload id", info.UploadID),
zap.Uint64("id", nodeID))
zap.Uint64("id", nodeID),
logs.TagField(logs.TagExternalStorageTree))
}
}
@ -1503,6 +1549,9 @@ func (c *Tree) AddPart(ctx context.Context, bktInfo *data.BucketInfo, multipartN
}
func (c *Tree) GetParts(ctx context.Context, bktInfo *data.BucketInfo, multipartNodeID uint64) ([]*data.PartInfoExtended, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.GetParts")
defer span.End()
parts, err := c.service.GetSubTree(ctx, bktInfo, systemTree, []uint64{multipartNodeID}, 2, false)
if err != nil {
return nil, err
@ -1514,7 +1563,8 @@ func (c *Tree) GetParts(ctx context.Context, bktInfo *data.BucketInfo, multipart
// multipart parts nodeID shouldn't have multiple values
c.reqLogger(ctx).Warn(logs.UnexpectedMultiNodeIDsInSubTreeMultiParts,
zap.Uint64("multipart node id ", multipartNodeID),
zap.Uint64s("node ids", part.GetNodeID()))
zap.Uint64s("node ids", part.GetNodeID()),
logs.TagField(logs.TagDatapath))
continue
}
if part.GetNodeID()[0] == multipartNodeID {
@ -1525,7 +1575,8 @@ func (c *Tree) GetParts(ctx context.Context, bktInfo *data.BucketInfo, multipart
c.reqLogger(ctx).Warn(logs.FailedToParsePartInfo,
zap.Uint64("multipart node id ", multipartNodeID),
zap.Uint64s("node ids", part.GetNodeID()),
zap.Error(err))
zap.Error(err),
logs.TagField(logs.TagDatapath))
continue
}
result = append(result, partInfo)
@ -1535,6 +1586,9 @@ func (c *Tree) GetParts(ctx context.Context, bktInfo *data.BucketInfo, multipart
}
func (c *Tree) PutBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo, addr oid.Address) ([]oid.Address, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.PutBucketLifecycleConfiguration")
defer span.End()
multiNode, err := c.getSystemNode(ctx, bktInfo, bucketLifecycleFilename)
isErrNotFound := errors.Is(err, tree.ErrNodeNotFound)
if err != nil && !isErrNotFound {
@ -1556,7 +1610,7 @@ func (c *Tree) PutBucketLifecycleConfiguration(ctx context.Context, bktInfo *dat
latest := multiNode.Latest()
ind := latest.GetLatestNodeIndex()
if latest.IsSplit() {
c.reqLogger(ctx).Error(logs.BucketLifecycleNodeHasMultipleIDs)
c.reqLogger(ctx).Error(logs.BucketLifecycleNodeHasMultipleIDs, logs.TagField(logs.TagDatapath))
}
if err = c.service.MoveNode(ctx, bktInfo, systemTree, latest.ID[ind], 0, meta); err != nil {
@ -1575,6 +1629,9 @@ func (c *Tree) PutBucketLifecycleConfiguration(ctx context.Context, bktInfo *dat
}
func (c *Tree) GetBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo) (oid.Address, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.GetBucketLifecycleConfiguration")
defer span.End()
node, err := c.getSystemNode(ctx, bktInfo, bucketLifecycleFilename)
if err != nil {
return oid.Address{}, fmt.Errorf("get lifecycle node: %w", err)
@ -1584,6 +1641,9 @@ func (c *Tree) GetBucketLifecycleConfiguration(ctx context.Context, bktInfo *dat
}
func (c *Tree) DeleteBucketLifecycleConfiguration(ctx context.Context, bktInfo *data.BucketInfo) ([]oid.Address, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.DeleteBucketLifecycleConfiguration")
defer span.End()
multiNode, err := c.getSystemNode(ctx, bktInfo, bucketLifecycleFilename)
isErrNotFound := errors.Is(err, tree.ErrNodeNotFound)
if err != nil && !isErrNotFound {
@ -1603,6 +1663,9 @@ func (c *Tree) DeleteBucketLifecycleConfiguration(ctx context.Context, bktInfo *
}
func (c *Tree) DeleteMultipartUpload(ctx context.Context, bktInfo *data.BucketInfo, multipartInfo *data.MultipartInfo) error {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.DeleteMultipartUpload")
defer span.End()
err := c.service.RemoveNode(ctx, bktInfo, systemTree, multipartInfo.ID)
if err != nil {
return err
@ -1614,6 +1677,9 @@ func (c *Tree) DeleteMultipartUpload(ctx context.Context, bktInfo *data.BucketIn
}
func (c *Tree) PutLock(ctx context.Context, bktInfo *data.BucketInfo, nodeID uint64, lock *data.LockInfo) error {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.PutLock")
defer span.End()
meta := map[string]string{isLockKV: "true"}
if lock.IsLegalHoldSet() {
@ -1636,6 +1702,9 @@ func (c *Tree) PutLock(ctx context.Context, bktInfo *data.BucketInfo, nodeID uin
}
func (c *Tree) GetLock(ctx context.Context, bktInfo *data.BucketInfo, nodeID uint64) (*data.LockInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.GetLock")
defer span.End()
lockNode, err := c.getTreeNode(ctx, bktInfo, nodeID, isLockKV)
if err != nil {
return nil, err
@ -1675,6 +1744,9 @@ func getLock(lockNode *treeNode) (*data.LockInfo, error) {
}
func (c *Tree) GetObjectTaggingAndLock(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion) (map[string]string, *data.LockInfo, error) {
ctx, span := tracing.StartSpanFromContext(ctx, "tree.GetObjectTaggingAndLock")
defer span.End()
nodes, err := c.getTreeNodes(ctx, bktInfo, objVersion.ID, isTagKV, isLockKV)
if err != nil {
return nil, nil, err
@ -1831,7 +1903,7 @@ func (c *Tree) getSystemNode(ctx context.Context, bktInfo *data.BucketInfo, name
return nil, tree.ErrNodeNotFound
}
if len(nodes) != 1 {
c.reqLogger(ctx).Warn(logs.FoundSeveralSystemNodes, zap.String("name", name))
c.reqLogger(ctx).Warn(logs.FoundSeveralSystemNodes, zap.String("name", name), logs.TagField(logs.TagDatapath))
}
return newMultiNode(nodes)