rgw追加写功能测试验证

背景

aws的s3以前是不支持追加写这个功能的

这个存储类型大概是2023年发布的,这个文章是2024年11月21日发布的

找到功能的发布大概时间

我们看下boto3的工具是什么时候集成进去的就知道这个功能发布的大概的时间

1
WriteOffsetBytes

aws里面使用这个参数去控制追加写的偏移量的,我们根据这个关键字去找

找到botcore的代码,使用git下载下来

1
cd  botocore/botocore/data/s3/2006-03-01

进入到这个目录,这个目录里面是aws的s3的接口文档,也就是不管是用python的sdk还是使用aws进行操作的时候,命令行的一些接口都是通过这个目录里面的json文件去判断的

找到这个文件的所有的历史提交

1
git log -p service-2.json

找到了这个地方的提交代码

1
2
3
4
5
6
7
8
9
10
@@ -9579,6 +9667,12 @@
"location":"uri",
"locationName":"Key"
},
+ "WriteOffsetBytes":{
+ "shape":"WriteOffsetBytes",
+ "documentation":"<p> Specifies the offset for appending data to existing objects in bytes. The offset must be equal to the size of the existing object being appended to. If no object exists, setting this header to 0 will create a new object. </p> <note> <p>This functionality is only supported for objects in the Amazon S3 Express One Zone storage class in directory buckets.</p> </note>",
+ "location":"header",
+ "locationName":"x-amz-write-offset-bytes"
+ },

找到5c8efd314eee44949f6a9e1fb8af522c654ded15这个提交

1
2
3
4
5
commit 5c8efd314eee44949f6a9e1fb8af522c654ded15
Author: aws-sdk-python-automation <[email protected]>
Date: Fri Nov 22 00:52:00 2024 +0000

Update to latest models

因为这个模型的提交很频繁,所以这个提交里面也不会单独说明这个功能

查看这次提交

1
git  show 5c8efd314eee44949f6a9e1fb8af522c654ded15  service-2.json

确认就是这个提交里面增加的

也就是上面的2024年的11月21日,跟上面的发布时间基本对上了,也就是那边发布了接口文档,这边适配几个关键字就可以使用相关的功能了,这个松耦合做的非常好

ceph这边的适配

我们看下官方文档

1
https://docs.ceph.com/en/reef/radosgw/s3/objectops/

返回的关键字是

1
x-rgw-next-append-position

那我们查询这个关键字就知道什么时候合入的这个功能的

最开始在2018年就提出来了,这个是阿里云的oss支持这个功能,然后ceph这边做了适配,这个地方从时间线上面aws官方这个发布的时间有点晚

在2019年的2月就合入了这个功能了,也就是基本上2019年2月后面的主发行版本应该都是支持这个功能的

功能的验证

官方的说明

https://github.com/ceph/ceph/blob/main/examples/rgw/boto3/README.md

1
2
3
This directory contains examples on how to use AWS CLI/boto3 to exercise the RadosGW extensions to the S3 API. This is an extension to the [AWS SDK](https://github.com/boto/botocore/blob/develop/botocore/data/s3/2006-03-01/service-2.json).

For the standard client to support these extensions, the `service-2.sdk-extras.json` file should be added. You can place it under the default folder `~/.aws/models/s3/2006-03-01/` or create a custom one `/path/to/custom/folder/models/s3/2006-03-01/` and add it to `AWS_DATA_PATH` environment variable. For more information see [here](https://github.com/boto/botocore/blob/develop/botocore/loaders.py#L33).

这个写的是,这个目录下面有怎样使用aws的客户端和boto3的例子,以及相关的api的扩展,一般兼容性s3接口的做法是基于s3的原生标准接口再做扩展操作,这个里面就是相关的api的文件,我们需要把ceph官方提供的service-2.sdk-extras.json,添加到正在使用的boto3的sdk里面去,具体操作下面会写

确定boto3的位置

这个地方很容易出问题,因为可能环境里面装了不同版本的boto3,可能重新安装就按照了新的版本,也有可能通过不同的命令安装了不同的版本,我们需要确定好我们正在使用的boto3的库在哪里

安装boto3

1
[root@lab101 site-packages]# pip3 install boto3==1.35.79  botocore==1.35.79  -i https://mirrors.aliyun.com/pypi/simple

这里我指定了安装的版本,因为aws对这个botocore的版本有要求,然后隔一天boto重新安装就升级了,可以通过上面的命令来按照指定的版本即可

确定路径

1
2
3
4
5
6
7
[root@lab101 site-packages]# python3
Python 3.9.6 (default, Dec 12 2024, 16:15:26)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import botocore
>>> print(botocore)
<module 'botocore' from '/usr/local/lib/python3.9/site-packages/botocore/__init__.py'>

可以看到路径在

1
/usr/local/lib/python3.9/site-packages/botocore

注意下我们的模型是在这个下面的botocore里面的

1
2
3
4
5
6
7
8
9
[root@lab101 2006-03-01]# ll /usr/local/lib/python3.9/site-packages/botocore/data/s3/2006-03-01/
total 248
-rw-r--r-- 1 root root 18305 Dec 13 10:48 endpoint-rule-set-1.json.gz
-rw-r--r-- 1 root root 57596 Dec 13 10:48 examples-1.json
-rw-r--r-- 1 root root 1837 Dec 13 10:48 paginators-1.json
-rw-r--r-- 1 root root 856 Dec 13 10:48 paginators-1.sdk-extras.json
-rw-r--r-- 1 root root 154059 Dec 13 10:48 service-2.json.gz
-rw-r--r-- 1 root root 98 Dec 13 10:48 service-2.sdk-extras.json
-rw-r--r-- 1 root root 1436 Dec 13 10:48 waiters-2.json

查看这个文件

1
2
3
4
5
6
7
8
9
[root@lab101 2006-03-01]# cat /usr/local/lib/python3.9/site-packages/botocore/data/s3/2006-03-01/service-2.sdk-extras.json
{
"version": 1.0,
"merge": {
"shapes": {
"Expires":{"type":"timestamp"}
}
}
}

可以看到这个文件是没有什么内容的,我们需要把ceph的扩展放到这个里面

这个去自己使用的ceph版本里面找,跨版本有可能出现当前版本没支持,后面版本支持的情况,所以尽量使用当前版本

1
https://github.com/ceph/ceph/blob/v15.2.17/examples/boto3/service-2.sdk-extras.json

比如上面这个是我要测试的版本的,下面是当前最新版本的

1
https://github.com/ceph/ceph/blob/main/examples/rgw/boto3/service-2.sdk-extras.json

可以看到路径发生了变化,把boto3的放到rgw下面去了,这个找下即可

1
2
3
[root@lab101 ~]#wget  https://raw.githubusercontent.com/ceph/ceph/refs/heads/main/examples/rgw/boto3/service-2.sdk-extras.json
[root@lab101 ~]# cp service-2.sdk-extras.json /usr/local/lib/python3.9/site-packages/botocore/data/s3/2006-03-01/service-2.sdk-extras.json
cp: overwrite ‘/usr/local/lib/python3.9/site-packages/botocore/data/s3/2006-03-01/service-2.sdk-extras.json’? y

替换

1
2
3
4
5
6
7
8
9
10
[root@lab101 2006-03-01]# cat service-2.sdk-extras.json |grep Append
"AppendPosition": {
"shape":"AppendPosition",
"Append": {
"shape":"Append",
"documentation":"<p>Append Object</p>",
"Append": {"type":"boolean"},
"AppendPosition":{"type":"integer"},
"AppendPosition": {
"shape":"AppendPosition",

检查确认下相关的关键字已经放入了,上面的就是确认有了

验证追加写功能

https://github.com/ceph/ceph/blob/v15.2.17/examples/boto3/append_object.py

这个是ceph官方提供的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/usr/bin/python
from __future__ import print_function

import boto3
import sys
import json

def js_print(arg):
print(json.dumps(arg, indent=2))

if len(sys.argv) != 3:
print('Usage: ' + sys.argv[0] + ' <bucket> <key>')
sys.exit(1)

# bucket name as first argument
bucketname = sys.argv[1]
keyname = sys.argv[2]
# endpoint and keys from vstart
endpoint = 'http://127.0.0.1:8000'
access_key='0555b35654ad1656d804'
secret_key='h7GhxuBLTrlhVUyxSPUKUV8r/2EI4ngqJxD7iBdBYLhwluN30JaT3Q=='

client = boto3.client('s3',
endpoint_url=endpoint,
aws_access_key_id=access_key,
aws_secret_access_key=secret_key)

print('deleting object first')
js_print(client.delete_object(Bucket=bucketname, Key=keyname))
print('appending at position 0')
resp = client.put_object(Bucket=bucketname, Key=keyname,
Append=True,
AppendPosition=0,
Body='8letters')

js_print(resp)
append_pos = resp['AppendPosition']
print('appending at position %d' % append_pos)
js_print(client.put_object(Bucket=bucketname, Key=keyname,
Append=True,
AppendPosition=append_pos,
Body='8letters'))

可以看下大概的内容

  • 先删除指定的名称的对象
  • 然后在0的位置写入8letters字符
  • 然后在获取返回的AppendPosition
  • 基于AppendPosition写入下一个8letters字符

看下aws的官方的例子

1
s3.put_object(Bucket='amzn-s3-demo-bucket--use2-az2--x-s3', Key='2024-11-05-sdk-test', Body=b'123456789', WriteOffsetBytes=9)

大部分是一样的,就是WriteOffsetBytes对应的是ceph的AppendPosition

修改成自己可以用的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
[root@lab101 ceph]# cat test.py
#!/usr/bin/python
from __future__ import print_function

import boto3
import sys
import json

def js_print(arg):
print(json.dumps(arg, indent=2))

if len(sys.argv) != 3:
print('Usage: ' + sys.argv[0] + ' <bucket> <key>')
sys.exit(1)

# bucket name as first argument
bucketname = sys.argv[1]
keyname = sys.argv[2]
# endpoint and keys from vstart
endpoint = 'http://192.168.0.101:7481'
access_key='test1'
secret_key='test1'

client = boto3.client('s3',
endpoint_url=endpoint,
aws_access_key_id=access_key,
aws_secret_access_key=secret_key)

print('deleting object first')
js_print(client.delete_object(Bucket=bucketname, Key=keyname))
print('appending at position 0')
resp = client.put_object(Bucket=bucketname, Key=keyname,
Append=True,
AppendPosition=0,
Body='8letters')

js_print(resp)
append_pos = resp['AppendPosition']
print('appending at position %d' % append_pos)
js_print(client.put_object(Bucket=bucketname, Key=keyname,
Append=True,
AppendPosition=append_pos,
Body='8letters'))

运行脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
[root@lab101 ceph]# python3 test.py testbucket myappend.txt
deleting object first
{
"ResponseMetadata": {
"RequestId": "tx000001eb3924743c3404f-00675ba47a-37bd-default",
"HostId": "",
"HTTPStatusCode": 204,
"HTTPHeaders": {
"x-amz-request-id": "tx000001eb3924743c3404f-00675ba47a-37bd-default",
"date": "Fri, 13 Dec 2024 03:05:30 GMT"
},
"RetryAttempts": 0
}
}
appending at position 0
{
"ResponseMetadata": {
"RequestId": "tx00000952a1aea29f3ea2f-00675ba47a-37bd-default",
"HostId": "",
"HTTPStatusCode": 200,
"HTTPHeaders": {
"content-length": "0",
"etag": "\"4310bc7480e027b4b5aede530e4ee9df\"",
"accept-ranges": "bytes",
"x-rgw-next-append-position": "8",
"x-amz-request-id": "tx00000952a1aea29f3ea2f-00675ba47a-37bd-default",
"date": "Fri, 13 Dec 2024 03:05:30 GMT"
},
"RetryAttempts": 0
},
"ETag": "\"4310bc7480e027b4b5aede530e4ee9df\"",
"AppendPosition": 8
}
appending at position 8
{
"ResponseMetadata": {
"RequestId": "tx00000bb44851eb1f0253d-00675ba47a-37bd-default",
"HostId": "",
"HTTPStatusCode": 200,
"HTTPHeaders": {
"content-length": "0",
"etag": "\"4310bc7480e027b4b5aede530e4ee9df\"",
"accept-ranges": "bytes",
"x-rgw-next-append-position": "16",
"x-amz-request-id": "tx00000bb44851eb1f0253d-00675ba47a-37bd-default",
"date": "Fri, 13 Dec 2024 03:05:30 GMT"
},
"RetryAttempts": 0
},
"ETag": "\"4310bc7480e027b4b5aede530e4ee9df\"",
"AppendPosition": 16
}

参数是一个bucket名,一个对象的名称

查看运行的效果

1
2
3
4
[root@lab101 ceph]# aws --endpoint=http://192.168.0.101:7481 s3 cp  s3://testbucket/myappend.txt  myappend.txt
download: s3://testbucket/myappend.txt to ./myappend.txt
[root@lab101 ceph]# cat myappend.txt
8letters8letters[root@lab101 ceph]#

可以看到追加写的功能成功了,确实有16个字符了

上面的是使用的boto3的sdk做的python的测试用例,看下aws的怎样使用

aws的追加写方法

1
https://docs.aws.amazon.com/AmazonS3/latest/userguide/directory-buckets-objects-append.html
1
aws s3api put-object --bucket amzn-s3-demo-bucket--azid--x-s3 --key sampleinput/file001.bin --body bucket-seed/file001.bin --write-offset-bytes size-of-sampleinput/file001.bin

这个是aws这边的推荐的方法,这个里面有个write-offset-bytes的关键字,这个就是对象的偏移量
规则是:
指定bucket名称 key指定对象的名称 body指定本地的文件 write-offset-bytes 这个指定偏移量的,也就是存在的对象的大小

1
2
3
4
[root@lab101 2006-03-01]# cat service-2.json |grep WriteOffsetBytes
"WriteOffsetBytes":{
"shape":"WriteOffsetBytes",
"WriteOffsetBytes":{

这个地方WriteOffsetBytes会在命令行这边解析为 –write-offset-bytes
规则就是大写字符和小写字符之间会插入一个-

这个是aws的这边的解析,在ceph这边是按AppendPosition和Append来解析的,那么我们可以尝试命令行用这个参数测试

1
2
3
4
5
6
7
[root@lab101 ~]# cat zpappend.txt
aaaaaaa
[root@lab101 ~]# aws s3api put-object --endpoint=http://192.168.0.101:7481 --bucket testbucket --key newappend.txt --body zpappend.txt --append --append-position 0
{
"ETag": "\"15fe514867dd5b4a1abf91ea35ff9e22\"",
"AppendPosition": 8
}

下载下来看看

1
2
3
4
[root@lab101 ceph]# aws --endpoint=http://192.168.0.101:7481 s3 cp s3://testbucket/newappend.txt newappend.txt
download: s3://testbucket/newappend.txt to ./newappend.txt
[root@lab101 ceph]# cat newappend.txt
aaaaaaa

再追加

1
2
3
4
5
[root@lab101 ~]# aws    s3api put-object   --endpoint=http://192.168.0.101:7481   --bucket testbucket --key newappend.txt --body zpappend.txt --append   --append-position 8
{
"ETag": "\"15fe514867dd5b4a1abf91ea35ff9e22\"",
"AppendPosition": 16
}

再下载

1
2
3
4
5
[root@lab101 ceph]# aws --endpoint=http://192.168.0.101:7481 s3 cp s3://testbucket/newappend.txt newappend.txt
download: s3://testbucket/newappend.txt to ./newappend.txt
[root@lab101 ceph]# cat newappend.txt
aaaaaaa
aaaaaaa

再测试

1
2
3
4
5
6
7
8
[root@lab101 ~]# aws    s3api put-object   --endpoint=http://192.168.0.101:7481   --bucket testbucket --key newappend.txt --body zpappend.txt --append   --append-position 8

An error occurred (PositionNotEqualToLength) when calling the PutObject operation: Unknown
[root@lab101 ~]# aws s3api put-object --endpoint=http://192.168.0.101:7481 --bucket testbucket --key newappend.txt --body zpappend.txt --append --append-position 16
{
"ETag": "\"15fe514867dd5b4a1abf91ea35ff9e22\"",
"AppendPosition": 24
}

可以看到,追加写的时候必须正确的指定已经存在的对象的大小位置,也就是偏移量,从哪里开始追加写

返回的位置就是最后的对象的大小

1
2
[root@lab101 ceph]# aws --endpoint=http://192.168.0.101:7481 s3 ls s3://testbucket/newappend.txt
2024-12-13 11:22:10 24 newappend.txt

可以看到追加写成功了

restapi追加写的功能

下面是正常的上传的代码,不是追加写的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
[root@lab101 ceph]# cat test1.py
#! /usr/bin/python3
# -*- coding:utf-8 -*-
import requests
import hashlib
import hmac
import datetime
import base64

# 配置 Ceph RGW 的访问信息
ACCESS_KEY = "test1"
SECRET_KEY = "test1"
RGW_ENDPOINT = "http://192.168.0.101:7481" # Ceph RADOS Gateway 的地址
BUCKET_NAME = "testbucket"
OBJECT_KEY = "testrest.txt"

# 计算 AWS S3 签名
def generate_signature(method, bucket, object_key, content_type, date, secret_key):
string_to_sign = f"{method}\n\n{content_type}\n{date}\n/{bucket}/{object_key}"
print("String to sign:", string_to_sign) # 调试信息
signature = hmac.new(secret_key.encode('utf-8'), string_to_sign.encode('utf-8'), hashlib.sha1).digest()
return base64.b64encode(signature).decode('utf-8')

# 上传对象到 Ceph
def upload_object(file_path):
with open(file_path, "rb") as f:
file_data = f.read()

content_type = "application/octet-stream"
date = datetime.datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S GMT")

signature = generate_signature(
method="PUT",
bucket=BUCKET_NAME,
object_key=OBJECT_KEY,
content_type=content_type,
date=date,
secret_key=SECRET_KEY
)

headers = {
"Content-Type": content_type,
"Date": date,
"Authorization": f"AWS {ACCESS_KEY}:{signature}"
}

url = f"{RGW_ENDPOINT}/{BUCKET_NAME}/{OBJECT_KEY}"

print("Request URL:", url) # 调试信息
print("Headers:", headers) # 调试信息

response = requests.put(url, headers=headers, data=file_data)

if response.status_code == 200:
print("Upload successful!")
else:
print(f"Upload failed! Status code: {response.status_code}, Response: {response.text}")

# 使用示例
if __name__ == "__main__":
upload_object("zp.txt")

我们再来一个追加写的restapi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
[root@lab101 ceph]# cat test4.py
#! /usr/bin/python3
# -*- coding:utf-8 -*-
import requests
import hashlib
import hmac
import datetime
import base64
import sys

# 配置 Ceph RGW 的访问信息
ACCESS_KEY = "test1"
SECRET_KEY = "test1"
RGW_ENDPOINT = "http://192.168.0.101:7481" # Ceph RADOS Gateway 的地址
BUCKET_NAME = "testbucket"

# 计算 AWS S3 签名
def generate_signature(method, bucket, object_key, content_type, date, secret_key):
string_to_sign = f"{method}\n\n{content_type}\n{date}\n/{bucket}/{object_key}"
print("String to sign:", string_to_sign) # 调试信息
signature = hmac.new(secret_key.encode('utf-8'), string_to_sign.encode('utf-8'), hashlib.sha1).digest()
return base64.b64encode(signature).decode('utf-8')

# 上传对象到 Ceph
def upload_object(object_key, file_path, position):
with open(file_path, "rb") as f:
file_data = f.read()

content_type = "application/octet-stream"
date = datetime.datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S GMT")

signature = generate_signature(
method="PUT",
bucket=BUCKET_NAME,
object_key=object_key,
content_type=content_type,
date=date,
secret_key=SECRET_KEY
)

headers = {
"Content-Type": content_type,
"Date": date,
"Authorization": f"AWS {ACCESS_KEY}:{signature}"
}

url = f"{RGW_ENDPOINT}/{BUCKET_NAME}/{object_key}?position={position}&append=true"

print("Request URL:", url) # 调试信息
print("Headers:", headers) # 调试信息

response = requests.put(url, headers=headers, data=file_data)

if response.status_code == 200:
print("Append successful!")
print("Response headers:", response.headers)
else:
print(f"Append failed! Status code: {response.status_code}, Response: {response.text}")

# 使用示例
if __name__ == "__main__":
if len(sys.argv) != 4:
print("Usage: python3 test4.py <object_key> <file_path> <position>")
sys.exit(1)

object_key = sys.argv[1]
file_path = sys.argv[2]
position = sys.argv[3]

upload_object(object_key, file_path, position)

可以看到签名的地方并没有改变,改变的就是请求的questsring发生了变化

看下运行效果

1
2
3
4
5
6
7
8
9
10
[root@lab101 ceph]# python3 test4.py  restaaa zp.txt 0
String to sign: PUT

application/octet-stream
Fri, 13 Dec 2024 07:13:33 GMT
/testbucket/restaaa
Request URL: http://192.168.0.101:7481/testbucket/restaaa?position=0&append=true
Headers: {'Content-Type': 'application/octet-stream', 'Date': 'Fri, 13 Dec 2024 07:13:33 GMT', 'Authorization': 'AWS test1:3J3okCbKD2WVsNpfngjMPqBmvVU='}
Append successful!
Response headers: {'Content-Length': '0', 'ETag': '"0a2806dcc91865a811d19150084f6de7"', 'Accept-Ranges': 'bytes', 'x-rgw-next-append-position': '16', 'x-amz-request-id': 'tx00000ec3108225d59c5e1-00675bde9d-37bd-default', 'Date': 'Fri, 13 Dec 2024 07:13:33 GMT', 'Connection': 'Keep-Alive'}

可以看到返回的x-rgw-next-append-position为16,也就是下个请求应该从16开始

查看文件大小

1
2
[root@lab101 site-packages]# aws --endpoint=http://192.168.0.101:7481 s3 ls s3://testbucket/restaaa
2024-12-13 15:13:33 16 restaaa

再次请求

1
2
3
4
5
6
7
8
9
10
[root@lab101 ceph]# python3 test4.py  restaaa zp.txt 16
String to sign: PUT

application/octet-stream
Fri, 13 Dec 2024 07:14:29 GMT
/testbucket/restaaa
Request URL: http://192.168.0.101:7481/testbucket/restaaa?position=16&append=true
Headers: {'Content-Type': 'application/octet-stream', 'Date': 'Fri, 13 Dec 2024 07:14:29 GMT', 'Authorization': 'AWS test1:PsYEAg3YnDb8PVBvRWSKL7nPhy0='}
Append successful!
Response headers: {'Content-Length': '0', 'ETag': '"0a2806dcc91865a811d19150084f6de7"', 'Accept-Ranges': 'bytes', 'x-rgw-next-append-position': '32', 'x-amz-request-id': 'tx00000cbe7cf038ff40327-00675bded5-37bd-default', 'Date': 'Fri, 13 Dec 2024 07:14:29 GMT', 'Connection': 'Keep-Alive'}

再次查看

1
2
[root@lab101 site-packages]# aws --endpoint=http://192.168.0.101:7481 s3 ls s3://testbucket/restaaa
2024-12-13 15:14:29 32 restaaa

可以看到文件追加写进去了

上面的例子是用的v2认证做的例子,v2认证比v4要简单一些

总结

本篇介绍了ceph这边的rgw的追加写的功能验证,可以看到跟aws类似,关键字做一点改变即可,本篇从三个工具讲述了ceph里面的追加写的功能,Python的boto的sdk的方法,aws的工具的方法,以及restapi的方法,从三个地方讲述了怎样发起追加写的请求,整体上还是比较简单就是对着指定的位置发起请求即可,就是请求的时候带上一个偏移量