)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"351012c68aa51037f644abdd7b88c0426b0c4e3a","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":54,"id":"eabfd99d_c99cb8e3","updated":"2026-08-07 14:47:54.000000000","message":"I think there are two issues in this patch:\n\n1. Conditional requests lose the 304. The object HEAD accepts HTTP_NOT_MODIFIED, and swob zeroes Content-Length on a 304. The controller reads that header without checking the status and returns an unconditional HTTPOk, so a 405-byte object answers If-None-Match with 200 and \u003cObjectSize\u003e0\u003c/ObjectSize\u003e.\n\n2. ETag is quoted. It\u0027s taken from the backend HEAD\u0027s ETag header, which carries the RFC 7232 quotes. GetObjectAttributes returns the ETag in the XML body, and AWS sends it bare there — compare the samples for get-object-attributes and head-object. resp.etag unquotes.\n\nIdeas how to change this in 1000106: sq? s3api: unquote GetObjectAttributes ETag, pass 304 through | https://review.opendev.org/c/openstack/swift/+/1000106\n\nI used the following on my SAIO to test:\n\n```\n#!/bin/bash\nexport AWS_ENDPOINT_URL\u003dhttp://localhost:8080\nexport AWS_ACCESS_KEY_ID\u003dtest:tester\nexport AWS_SECRET_ACCESS_KEY\u003dtesting\nexport AWS_DEFAULT_REGION\u003dus-east-1\n\naws s3api create-bucket --bucket attrs-test\nhead -c 405 /dev/urandom \u003e /tmp/obj405\naws s3api put-object --bucket attrs-test --key doc1 --body /tmp/obj405\n\necho\necho \"\u003d\u003d ETag via head-object: quoted is correct, it is an HTTP header\"\naws s3api head-object --bucket attrs-test --key doc1 --query ETag --output text\n\necho\necho \"\u003d\u003d ETag via get-object-attributes: quoted is the bug, AWS returns it bare\"\naws s3api get-object-attributes --bucket attrs-test --key doc1 \\\n    --object-attributes ETag ObjectSize\n\necho\necho \"\u003d\u003d conditional: AWS returns 304 no body, this returns 200 + ObjectSize 0\"\npython3 \u003c\u003c\u0027PY\u0027\nimport urllib.request\nimport urllib.error\n\nimport boto3\nfrom botocore.auth import S3SigV4Auth\nfrom botocore.awsrequest import AWSRequest\nfrom botocore.credentials import Credentials\n\nendpoint \u003d \u0027http://localhost:8080\u0027\nurl \u003d endpoint + \u0027/attrs-test/doc1?attributes\u0027\n\ns3 \u003d boto3.client(\u0027s3\u0027, endpoint_url\u003dendpoint,\n                  aws_access_key_id\u003d\u0027test:tester\u0027,\n                  aws_secret_access_key\u003d\u0027testing\u0027,\n                  region_name\u003d\u0027us-east-1\u0027)\netag \u003d s3.head_object(Bucket\u003d\u0027attrs-test\u0027, Key\u003d\u0027doc1\u0027)[\u0027ETag\u0027]\n\n# botocore has no If-None-Match parameter for GetObjectAttributes,\n# so sign and send the request by hand.\nsigned \u003d AWSRequest(\u0027GET\u0027, url, headers\u003d{\n    \u0027x-amz-object-attributes\u0027: \u0027ETag,ObjectSize\u0027,\n    \u0027If-None-Match\u0027: etag})\nS3SigV4Auth(Credentials(\u0027test:tester\u0027, \u0027testing\u0027), \u0027s3\u0027, \u0027us-east-1\u0027).add_auth(signed)\n\nreq \u003d urllib.request.Request(url)\nfor header, value in signed.headers.items():\n    req.add_header(header, value)\n\ntry:\n    resp \u003d urllib.request.urlopen(req)\n    print(resp.status, resp.read().decode())\nexcept urllib.error.HTTPError as err:\n    print(err.code, err.read().decode())\nPY\n\naws s3api delete-object --bucket attrs-test --key doc1\naws s3api delete-bucket --bucket attrs-test\nrm -f /tmp/obj405\n```","commit_id":"78e9825ae7400c937f76e6ff903c68938fbd53ab"},{"author":{"_account_id":39146,"name":"Nathaniel Martes","display_name":"Nate Martes","email":"nmartes@NVIDIA.com","username":"nmartes"},"change_message_id":"49b4ecd8939de98db4ba062d5ae8bf755cbc9ebc","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":54,"id":"0f3be95f_72a2b3da","in_reply_to":"eabfd99d_c99cb8e3","updated":"2026-08-07 20:26:33.000000000","message":"Yep AWS does return it bare:\n\n```\naws s3api get-object-attributes --bucket my-bucket --key foo --object-attributes ETag\n{\n    \"LastModified\": \"2026-08-07T20:22:24+00:00\",\n    \"ETag\": \"de059f09dd7963c5d65fa24f2df25d38\"\n}\n```\nWe also do need to handle the 304s, thanks for this review! I\u0027ll take a look at your sq? and see what I can use :)","commit_id":"78e9825ae7400c937f76e6ff903c68938fbd53ab"},{"author":{"_account_id":39146,"name":"Nathaniel Martes","display_name":"Nate Martes","email":"nmartes@NVIDIA.com","username":"nmartes"},"change_message_id":"b0517cd5ab63e9e9a43f17cc5cb913aa14c2c3b9","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":55,"id":"d4cb9d7d_0b88dbc2","updated":"2026-08-07 20:56:26.000000000","message":"@cschwede@nvidia.com I squashed in 1000106: sq? s3api: unquote GetObjectAttributes ETag, pass 304 through | https://review.opendev.org/c/openstack/swift/+/1000106 which the changes were good, I also went ahead and added some compat tests to match up with the unit tests","commit_id":"7c433216eb011d831418ac99335c0124702a6e39"}],"swift/common/middleware/s3api/s3request.py":[{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"e70da2a204825c55e76dab1a39660b87d8ea24df","unresolved":false,"context_lines":[{"line_number":1597,"context_line":"                \u0027checksum algorithm.\u0027 % ("},{"line_number":1598,"context_line":"                    checksum_type, checksum_algorithm.lower()))"},{"line_number":1599,"context_line":"        if checksum_type \u003d\u003d CHECKSUM_TYPE_FULL_OBJECT:"},{"line_number":1600,"context_line":"            checksum_header \u003d checksum_header_from_algorithm(checksum_algorithm)"},{"line_number":1601,"context_line":"            try:"},{"line_number":1602,"context_line":"                checksum_hasher \u003d CHECKSUMS_BY_HEADER[checksum_header]()"},{"line_number":1603,"context_line":"            except NotImplementedError:"}],"source_content_type":"text/x-python","patch_set":12,"id":"56d21515_6a12152e","line":1600,"updated":"2026-07-09 19:29:03.000000000","message":"pep8: E501 line too long (80 \u003e 79 characters)","commit_id":"5a32cedd3f1fc6c72060c3d4e58f871a550a5b4d"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"3dc7b2ba8f020ec6fe6d9e084df52b9c2744cd26","unresolved":false,"context_lines":[{"line_number":1921,"context_line":"        Install a callback to persist checksum metadata after validation."},{"line_number":1922,"context_line":"        If checksum calculation fails (say for instance, the connection drops),"},{"line_number":1923,"context_line":"        the PUT fails before a checksum is recorded, so no checksum sysmeta is"},{"line_number":1924,"context_line":"        persisted. Once Swift reads in the entire object, the checksum is finshed"},{"line_number":1925,"context_line":"        and sysmeta is attached to the uploaded whole-object body."},{"line_number":1926,"context_line":"        :param checksum_type: The type of checksum to persist."},{"line_number":1927,"context_line":"        \"\"\""}],"source_content_type":"text/x-python","patch_set":24,"id":"57016ddd_f8fc4c37","line":1924,"updated":"2026-07-20 22:26:51.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"8cb30f50a8019221946c5777d0e0cea406838f37"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"3dc7b2ba8f020ec6fe6d9e084df52b9c2744cd26","unresolved":false,"context_lines":[{"line_number":1940,"context_line":"    def calculate_checksum_metadata(self, checksum_header,"},{"line_number":1941,"context_line":"                                    checksum_type\u003dNone):"},{"line_number":1942,"context_line":"        \"\"\""},{"line_number":1943,"context_line":"        Specify that in this S3Request, we need to calcuate the checksum for the"},{"line_number":1944,"context_line":"        object because the client did not specify any checksum algorithm."},{"line_number":1945,"context_line":"        :param checksum_header: The header to calcuated for this object."},{"line_number":1946,"context_line":"        :param checksum_type: The type of this checksum for this object."}],"source_content_type":"text/x-python","patch_set":24,"id":"1825ae11_497b0cf3","line":1943,"updated":"2026-07-20 22:26:51.000000000","message":"pep8: E501 line too long (80 \u003e 79 characters)","commit_id":"8cb30f50a8019221946c5777d0e0cea406838f37"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"3bc114018e69dfc091da44f6d786e906cc780ca0","unresolved":false,"context_lines":[{"line_number":1764,"context_line":"        here."},{"line_number":1765,"context_line":"        \"\"\""},{"line_number":1766,"context_line":"        return ("},{"line_number":1767,"context_line":"            self._should_handle_request_checksum(self.method) and \\"},{"line_number":1768,"context_line":"            checksum_header is None and"},{"line_number":1769,"context_line":"            checksum_trailer is None)"},{"line_number":1770,"context_line":""}],"source_content_type":"text/x-python","patch_set":26,"id":"c05de72d_203e35a6","line":1767,"updated":"2026-07-21 01:17:28.000000000","message":"pep8: E502 the backslash is redundant between brackets","commit_id":"4d5447c26c5ad98c58961c1775e04819816fac2c"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"2f442bc1ced69dd4a05987e81a43a309b2375dcf","unresolved":false,"context_lines":[{"line_number":1935,"context_line":"        Specify that in this S3Request, we need to calculate the checksum for"},{"line_number":1936,"context_line":"        the object because the client did not specify any checksum algorithm."},{"line_number":1937,"context_line":""},{"line_number":1938,"context_line":"        If request parsing already installed a CalculatingChecksumInput wrapper,"},{"line_number":1939,"context_line":"        replace that wrapper rather than putting another on top of it."},{"line_number":1940,"context_line":"        Let\u0027s not compute two checksum algorithms for one S3Request. Controllers"},{"line_number":1941,"context_line":"        may discover a more specific checksum algorithm, such as an UploadPart request"}],"source_content_type":"text/x-python","patch_set":31,"id":"5c8a79bd_c4d06487","line":1938,"updated":"2026-07-21 23:52:14.000000000","message":"pep8: E501 line too long (80 \u003e 79 characters)","commit_id":"f79fe8571a7981589b45d0c4ef485d7929e3e0ae"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"2f442bc1ced69dd4a05987e81a43a309b2375dcf","unresolved":false,"context_lines":[{"line_number":1937,"context_line":""},{"line_number":1938,"context_line":"        If request parsing already installed a CalculatingChecksumInput wrapper,"},{"line_number":1939,"context_line":"        replace that wrapper rather than putting another on top of it."},{"line_number":1940,"context_line":"        Let\u0027s not compute two checksum algorithms for one S3Request. Controllers"},{"line_number":1941,"context_line":"        may discover a more specific checksum algorithm, such as an UploadPart request"},{"line_number":1942,"context_line":"        deciding to use the algorithm on the MPU manifest, before the body is read."},{"line_number":1943,"context_line":""}],"source_content_type":"text/x-python","patch_set":31,"id":"e081a4a8_a440ebf1","line":1940,"updated":"2026-07-21 23:52:14.000000000","message":"pep8: E501 line too long (80 \u003e 79 characters)","commit_id":"f79fe8571a7981589b45d0c4ef485d7929e3e0ae"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"2f442bc1ced69dd4a05987e81a43a309b2375dcf","unresolved":false,"context_lines":[{"line_number":1938,"context_line":"        If request parsing already installed a CalculatingChecksumInput wrapper,"},{"line_number":1939,"context_line":"        replace that wrapper rather than putting another on top of it."},{"line_number":1940,"context_line":"        Let\u0027s not compute two checksum algorithms for one S3Request. Controllers"},{"line_number":1941,"context_line":"        may discover a more specific checksum algorithm, such as an UploadPart request"},{"line_number":1942,"context_line":"        deciding to use the algorithm on the MPU manifest, before the body is read."},{"line_number":1943,"context_line":""},{"line_number":1944,"context_line":"        :param checksum_header: The header to calculate for this object."}],"source_content_type":"text/x-python","patch_set":31,"id":"9f9ec981_02ded1cd","line":1941,"updated":"2026-07-21 23:52:14.000000000","message":"pep8: E501 line too long (86 \u003e 79 characters)","commit_id":"f79fe8571a7981589b45d0c4ef485d7929e3e0ae"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"2f442bc1ced69dd4a05987e81a43a309b2375dcf","unresolved":false,"context_lines":[{"line_number":1939,"context_line":"        replace that wrapper rather than putting another on top of it."},{"line_number":1940,"context_line":"        Let\u0027s not compute two checksum algorithms for one S3Request. Controllers"},{"line_number":1941,"context_line":"        may discover a more specific checksum algorithm, such as an UploadPart request"},{"line_number":1942,"context_line":"        deciding to use the algorithm on the MPU manifest, before the body is read."},{"line_number":1943,"context_line":""},{"line_number":1944,"context_line":"        :param checksum_header: The header to calculate for this object."},{"line_number":1945,"context_line":"        :param checksum_type: The type of this checksum for this object."}],"source_content_type":"text/x-python","patch_set":31,"id":"c1f1594a_1a1144fd","line":1942,"updated":"2026-07-21 23:52:14.000000000","message":"pep8: E501 line too long (83 \u003e 79 characters)","commit_id":"f79fe8571a7981589b45d0c4ef485d7929e3e0ae"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"e58ad455ac1a5eb46bc3effc98a0692707235701","unresolved":false,"context_lines":[{"line_number":118,"context_line":"    \u0027x-static-large-object\u0027,"},{"line_number":119,"context_line":"))"},{"line_number":120,"context_line":""},{"line_number":121,"context_line":"def _get_checksum_hasher(header):"},{"line_number":122,"context_line":"    try:"},{"line_number":123,"context_line":"        return CHECKSUMS_BY_HEADER[header]()"},{"line_number":124,"context_line":"    except (KeyError, NotImplementedError):"}],"source_content_type":"text/x-python","patch_set":44,"id":"51ffb80a_42721dd0","line":121,"updated":"2026-07-28 19:00:56.000000000","message":"pep8: E302 expected 2 blank lines, found 1","commit_id":"81284287df0ffe3d4b60f037fb0af9e541abc04d"}],"test/s3api/test_object_checksums.py":[{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"f8045cecc89cb3771d435a4bccb3e04c55b354e3","unresolved":false,"context_lines":[{"line_number":783,"context_line":"        self.assertNotIn(\u0027StorageClass\u0027, resp)"},{"line_number":784,"context_line":"        self.assertNotIn(\u0027ObjectSize\u0027, resp)"},{"line_number":785,"context_line":""},{"line_number":786,"context_line":"class ObjectChecksumMPUCompositeMixin(object):"},{"line_number":787,"context_line":""},{"line_number":788,"context_line":"    def test_mpu_create_composite_checksum_type_headers(self):"},{"line_number":789,"context_line":"        self._test_mpu_create_checksum_type_headers(\u0027COMPOSITE\u0027)"}],"source_content_type":"text/x-python","patch_set":8,"id":"e6f6aeca_131ed896","line":786,"updated":"2026-07-06 20:41:46.000000000","message":"pep8: E302 expected 2 blank lines, found 1","commit_id":"15abf0494f3b586f8483baa84a31095bc800b6e1"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"e70da2a204825c55e76dab1a39660b87d8ea24df","unresolved":false,"context_lines":[{"line_number":1451,"context_line":"                \u0027recomputation\u0027)"},{"line_number":1452,"context_line":""},{"line_number":1453,"context_line":"        algorithm \u003d \u0027CRC32\u0027"},{"line_number":1454,"context_line":"        src_name \u003d self.create_name(\u0027mpu-copy-full-object-checksum-mismatch-src\u0027)"},{"line_number":1455,"context_line":"        dst_name \u003d self.create_name(\u0027mpu-copy-full-object-checksum-mismatch-dst\u0027)"},{"line_number":1456,"context_line":"        put_resp \u003d self.client.put_object("},{"line_number":1457,"context_line":"            Bucket\u003dself.bucket_name,"}],"source_content_type":"text/x-python","patch_set":12,"id":"3b5947ed_a17c0975","line":1454,"updated":"2026-07-09 19:29:03.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"5a32cedd3f1fc6c72060c3d4e58f871a550a5b4d"},{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"e70da2a204825c55e76dab1a39660b87d8ea24df","unresolved":false,"context_lines":[{"line_number":1452,"context_line":""},{"line_number":1453,"context_line":"        algorithm \u003d \u0027CRC32\u0027"},{"line_number":1454,"context_line":"        src_name \u003d self.create_name(\u0027mpu-copy-full-object-checksum-mismatch-src\u0027)"},{"line_number":1455,"context_line":"        dst_name \u003d self.create_name(\u0027mpu-copy-full-object-checksum-mismatch-dst\u0027)"},{"line_number":1456,"context_line":"        put_resp \u003d self.client.put_object("},{"line_number":1457,"context_line":"            Bucket\u003dself.bucket_name,"},{"line_number":1458,"context_line":"            Key\u003dsrc_name,"}],"source_content_type":"text/x-python","patch_set":12,"id":"7b259f27_80baaf3a","line":1455,"updated":"2026-07-09 19:29:03.000000000","message":"pep8: E501 line too long (81 \u003e 79 characters)","commit_id":"5a32cedd3f1fc6c72060c3d4e58f871a550a5b4d"}],"test/unit/common/middleware/s3api/test_s3request.py":[{"author":{"_account_id":22348,"name":"Zuul","username":"zuul","tags":["SERVICE_USER"]},"tag":"autogenerated:zuul:check","change_message_id":"e70da2a204825c55e76dab1a39660b87d8ea24df","unresolved":false,"context_lines":[{"line_number":2769,"context_line":"                                     \u0027X-Amz-Checksum-Type\u0027: \u0027full_object\u0027})"},{"line_number":2770,"context_line":""},{"line_number":2771,"context_line":"        with mock.patch.object(checksum, \u0027crc32_combine_anycrc\u0027, None):"},{"line_number":2772,"context_line":"            with self.assertRaises(S3NotImplemented) as cm:"},{"line_number":2773,"context_line":"                S3Request(req.environ)"},{"line_number":2774,"context_line":""},{"line_number":2775,"context_line":"    def test_mpu_initiate_crc32_default_checksum_type(self):"}],"source_content_type":"text/x-python","patch_set":12,"id":"5013d5e1_79699284","line":2772,"updated":"2026-07-09 19:29:03.000000000","message":"pep8: F841 local variable \u0027cm\u0027 is assigned to but never used","commit_id":"5a32cedd3f1fc6c72060c3d4e58f871a550a5b4d"}]}
