1. 找出对应bucket的owner

# radosgw-admin bucket list

# radosgw-admin bucket list --uid=ivan
[
    "ivan_backup",
    "ivan_bucket"
]


# radosgw-admin metadata  list user

# radosgw-admin bucket stats --bucket=test_bucket
{
    "bucket": "test_bucket",
    "pool": "default.rgw.buckets.data",
    "index_pool": "default.rgw.buckets.index",
    "id": "5c470d18-0d9e-4a34-8a6c-7a6d64784c3e.38311.41",
    "marker": "5c470d18-0d9e-4a34-8a6c-7a6d64784c3e.38311.41",
    "owner": "FFF65F671D9E48F696C5E57931A1DE85",
    "ver": "0#1",
    "master_ver": "0#0",
    "mtime": "2018-05-03 17:47:31.329045",
    "max_marker": "0#",
    "usage": {},
    "bucket_quota": {
        "enabled": false,
        "max_size_kb": -1,
        "max_objects": -1
    }
}

# radosgw-admin user info --uid=FFF65F671D9E48F696C5E57931A1DE85
{
    "user_id": "FFF65F671D9E48F696C5E57931A1DE85",
    "display_name": "user name",
    "email": "",
    "suspended": 0,
    "max_buckets": 1000,
    "auid": 0,
    "subusers": [],
    "keys": [
        {
            "user": "FFF65F671D9E48F696C5E57931A1DE85",
            "access_key": "A5DXX3XCFX7OF4ZM1UY5",
            "secret_key": "r1eeS2Sq5fuQF8JbOxjlauNDkPV7apZ6sArhFlUq"
        }
    ],
    "swift_keys": [],
    "caps": [],
    "op_mask": "read, write, delete",
    "default_placement": "",
    "placement_tags": [],
    "bucket_quota": {
        "enabled": false,
        "max_size_kb": -1,
        "max_objects": -1
    },
    "user_quota": {
        "enabled": false,
        "max_size_kb": -1,
        "max_objects": -1
    },
    "temp_url_keys": []
}

# radosgw-admin user info --uid=admin
{
    "user_id": "admin",
    "display_name": "admin",
    "email": "",
    "suspended": 0,
    "max_buckets": 1000,
    "auid": 0,
    "subusers": [],
    "keys": [
        {
            "user": "admin",
            "access_key": "HWG19J1AMB2MXU1YD0FW",
            "secret_key": "vDahq8wV7MZPOhbLzQKuQ2cXldH2MmXeA89nIXZN"
        }
    ],
    "swift_keys": [],
    "caps": [
        {
            "type": "buckets",
            "perm": "*"
        },
        {
            "type": "metadata",
            "perm": "*"
        },
        {
            "type": "usage",
            "perm": "*"
        },
        {
            "type": "users",
            "perm": "*"
        },
        {
            "type": "zone",
            "perm": "*"
        }
    ],
    "op_mask": "read, write, delete",
    "default_placement": "",
    "placement_tags": [],
    "bucket_quota": {
        "enabled": false,
        "max_size_kb": -1,
        "max_objects": -1
    },
    "user_quota": {
        "enabled": false,
        "max_size_kb": -1,
        "max_objects": -1
    },
    "temp_url_keys": []
}

2. 删除bucket

# radosgw-admin bucket rm --bucket=ivan_bucket --purge-objects
# radosgw-admin bucket rm --bucket=ivan_backup --purge-objects

3. bucket object管理

  • 基本管理
# radosgw-admin bucket list 
[
    "testab"
]

# radosgw-admin bucket list --bucket=testab
[
    {
        "name": "testobj.2~s7xdt9V5PGMqRXpd__fCQ06_Z_rTRTB.meta",
        "instance": "",
        "namespace": "multipart",
        "owner": "0B3E5561D9D146B9BA07A73D5B08B61A",
        "owner_display_name": "app_0B3E5561D9D146B9BA07A73D5B08B61A",
        "size": 0,
        "mtime": "2018-05-24 02:44:46.295558Z",
        "etag": "",
        "content_type": "binary\/octet-stream",
        "tag": "_vp71VTOQeUYHEmtXF_75Pr6gYBcJHQn",
        "flags": 0,
        "user_data": ""
    },
    {
        "name": "helloworld_1",
        "instance": "",
        "namespace": "",
        "owner": "0B3E5561D9D146B9BA07A73D5B08B61A",
        "owner_display_name": "app_0B3E5561D9D146B9BA07A73D5B08B61A",
        "size": 129,
        "mtime": "2018-05-24 07:36:37.854243Z",
        "etag": "a2922a8dd298d6cc615cbbe25628df6e",
        "content_type": "binary\/octet-stream",
        "tag": "78b9bcbb-b328-4e4a-8417-4ed933be88bb.70594.16",
        "flags": 0,
        "user_data": ""
    }
]

//删除bucket object
# radosgw-admin object rm --bucket=testab --object=helloworld_1
  • 查看object的ACL信息
radosgw-admin object stat --bucket=jyeammolddraw --object=JH014121_v1.0.x_t
{
    "name": "JH014121_v1.0.x_t",
    "size": 9134263,
    "policy": {
        "acl": {
            "acl_user_map": [
                {
                    "user": "0A0B72E2B9374E148DA9D8486B975D2C",
                    "acl": 15
                }
            ],
            "acl_group_map": [],
            "grant_map": [
                {
                    "id": "0A0B72E2B9374E148DA9D8486B975D2C",
                    "grant": {
                        "type": {
                            "type": 0
                        },
                        "id": "0A0B72E2B9374E148DA9D8486B975D2C",
                        "email": "",
                        "permission": {
                            "flags": 15
                        },
                        "name": "app_0A0B72E2B9374E148DA9D8486B975D2C",
                        "group": 0
                    }
                }
            ]
        },
        "owner": {
            "id": "0A0B72E2B9374E148DA9D8486B975D2C",
            "display_name": "app_0A0B72E2B9374E148DA9D8486B975D2C"
        }
    },
    "etag": "45fcbd2bb460b40bc5dbcd226c51b749-18\u0000",
    "tag": "135882fc-2865-43ab-9f71-7dd4b2095406.20040920.653647\u0000",
    "manifest": {
        "objs": [],
        "obj_size": 9134263,
        "explicit_objs": "false",
        "head_obj": {
            "bucket": {
                "name": "jyeammolddraw",
                "pool": "nanhai-01.rgw.buckets.data",
                "data_extra_pool": "nanhai-01.rgw.buckets.non-ec",
                "index_pool": "nanhai-01.rgw.buckets.index",
                "marker": "135882fc-2865-43ab-9f71-7dd4b2095406.20035088.1163",
                "bucket_id": "135882fc-2865-43ab-9f71-7dd4b2095406.20035088.1163",
                "tenant": ""
            },
            "key": "",
            "ns": "",
            "object": "JH014121_v1.0.x_t",
            "instance": "",
            "orig_obj": "JH014121_v1.0.x_t"
        },
        "head_size": 0,
        "max_head_size": 0,
        "prefix": "JH014121_v1.0.x_t.2~f0A3ei1JrJxM381Q4Rg2YVmraISjSBC",
        "tail_bucket": {
            "name": "jyeammolddraw",
            "pool": "nanhai-01.rgw.buckets.data",
            "data_extra_pool": "nanhai-01.rgw.buckets.non-ec",
            "index_pool": "nanhai-01.rgw.buckets.index",
            "marker": "135882fc-2865-43ab-9f71-7dd4b2095406.20035088.1163",
            "bucket_id": "135882fc-2865-43ab-9f71-7dd4b2095406.20035088.1163",
            "tenant": ""
        },
        "rules": [
            {
                "key": 0,
                "val": {
                    "start_part_num": 1,
                    "start_ofs": 0,
                    "part_size": 524288,
                    "stripe_max_size": 4194304,
                    "override_prefix": ""
                }
            },
            {
                "key": 8912896,
                "val": {
                    "start_part_num": 18,
                    "start_ofs": 8912896,
                    "part_size": 221367,
                    "stripe_max_size": 4194304,
                    "override_prefix": ""
                }
            }
        ],
        "tail_instance": ""
    },
    "attrs": {
        "user.rgw.content_type": "binary\/octet-stream\u0000",
        "user.rgw.pg_ver": "p>\u0000\u0000\u0000\u0000\u0000\u0000",
        "user.rgw.source_zone": "\u000ei\u0010
        "user.rgw.x-amz-acl": "private\u0000"
    }
}

3. 用户管理

//1)创建s3用户
# radosgw-admin user create --uid='testuser' --display-name="testuser" --key-type=s3 
{
    "user_id": "testuser",
    "display_name": "testuser",
    "email": "",
    "suspended": 0,
    "max_buckets": 1000,
    "auid": 0,
    "subusers": [],
    "keys": [
        {
            "user": "testuser",
            "access_key": "N7S1NKVRU3ZCX1I06QGE",
            "secret_key": "j5Zc1qFISWmWqenwRVeMBQCh8kVB8bAhj9q5PLus"
        }
    ],
    "swift_keys": [],
    "caps": [],
    "op_mask": "read, write, delete",
    "default_placement": "",
    "placement_tags": [],
    "bucket_quota": {
        "enabled": false,
        "max_size_kb": -1,
        "max_objects": -1
    },
    "user_quota": {
        "enabled": false,
        "max_size_kb": -1,
        "max_objects": -1
    },
    "temp_url_keys": []
}

//2) 创建swift用户key
# radosgw-admin subuser create --subuser=testuser:swift --key-type=swift 
{
    "user_id": "testuser",
    "display_name": "testuser",
    "email": "",
    "suspended": 0,
    "max_buckets": 1000,
    "auid": 0,
    "subusers": [
        {
            "id": "testuser:swift",
            "permissions": "<none>"
        }
    ],
    "keys": [
        {
            "user": "testuser",
            "access_key": "N7S1NKVRU3ZCX1I06QGE",
            "secret_key": "j5Zc1qFISWmWqenwRVeMBQCh8kVB8bAhj9q5PLus"
        }
    ],
    "swift_keys": [
        {
            "user": "testuser:swift",
            "secret_key": "QosX7LgFtkvPj6eBDeOs0SQdP43MP3gatYA5SdoS"
        }
    ],
    "caps": [],
    "op_mask": "read, write, delete",
    "default_placement": "",
    "placement_tags": [],
    "bucket_quota": {
        "enabled": false,
        "max_size_kb": -1,
        "max_objects": -1
    },
    "user_quota": {
        "enabled": false,
        "max_size_kb": -1,
        "max_objects": -1
    },
    "temp_url_keys": []
}

//3) 删除s3用户
# radosgw-admin user rm --uid='testuser'  --access-key=N7S1NKVRU3ZCX1I06QGE


//4) 列出所有的用户
# radosgw-admin metadata  list user

//5) 删除用户及其对应的bucket数据
# radosgw-admin user rm --uid="909A284903754DAD910C68F9AFA2BE81" --purge-data

4. rgw配额管理

# radosgw-admin user info --uid=app_a9d2c0a01c46443f_g_715
{
    "user_id": "app_a9d2c0a01c46443f_g_715",
    "display_name": "app_a9d2c0a01c46443f_g_715",
    "email": "",
    "suspended": 0,
    "max_buckets": 1,
    "auid": 0,
    "subusers": [],
    "keys": [
        {
            "user": "app_a9d2c0a01c46443f_g_715",
            "access_key": "Z5WE64O1FSIX385T2FFU",
            "secret_key": "4Vkl4aQ9Rwj5zDwRTIQTVYb2SfENVssayTezbSpv"
        }
    ],
    "swift_keys": [],
    "caps": [
        {
            "type": "buckets",
            "perm": "read"
        },
        {
            "type": "metadata",
            "perm": "*"
        }
    ],
    "op_mask": "read, write, delete",
    "default_placement": "",
    "placement_tags": [],
    "bucket_quota": {
        "enabled": false,
        "max_size_kb": -1,
        "max_objects": -1
    },
    "user_quota": {
        "enabled": true,
        "max_size_kb": 4294967296,
        "max_objects": -1
    },
    "temp_url_keys": []
}

# radosgw-admin quota set --max-objects=-1 --max-size=-1 --quota-scope=user --uid=app_a9d2c0a01c46443f_g_715
# radosgw-admin quota set --max-objects=-1 --max-size=-1 --quota-scope=bucket --bucket=app_a9d2c0a01c46443f_g_715_bucket
# radosgw-admin quota disable --quota-scope=user --uid=app_a9d2c0a01c46443f_g_71
# radosgw-admin quota enable --quota-scope=user --uid=app_a9d2c0a01c46443f_g_715
# radosgw-admin user stats --uid=app_a9d2c0a01c46443f_g_71 --sync-stats     //同步

5. 查看某一个池的副本数

# ceph osd pool get .rgw.buckets size
size: 3

6. 安装boto

# pip install boto

7. python调用s3接口

7.1 测试调用s3cmd

import boto
import boto.s3.connection
access_key = 'A5DXX3XCFX7OF4ZM1UY5'
secret_key = 'r1eeS2Sq5fuQF8JbOxjlauNDkPV7apZ6sArhFlUq'


conn = boto.connect_s3(
        aws_access_key_id = access_key,
        aws_secret_access_key = secret_key,
        host = '10.17.155.100',
        port = 7480,
        is_secure=False,               # uncomment if you are not using ssl
        calling_format = boto.s3.connection.OrdinaryCallingFormat(),
        )




# list bucket
print 'List all buckets:'
buckets = conn.get_all_buckets()
for bucket in buckets:
    print bucket.name

运行(主要这里只能列出owner为上述ak/sk的bucket):

List all buckets:
chaoge_100_test
chaoge_10_test
chaoge_11_test
chaoge_25_test
chaoge_26_test
chaoge_27_test
chaoge_28_test
chaoge_29_test
chaoge_2_test
chaoge_55_test
chaoge_56_test
chaoge_57_test
chaoge_58_test
chaoge_59_test
chaoge_5_test
chaoge_60_test
chaoge_61_test
chaoge_62_test
chaoge_63_test
chaoge_64_test
chaoge_65_test
chaoge_66_test
chaoge_67_test
chaoge_68_test

7.2 鉴权

s3_client.py文件:

#!/usr/bin/env python
# -*- coding:utf-8 -*-


import os
import sys
import httplib
import hashlib
import time
import json
import datetime
import hmac
import base64

s3ParamsToSign = {
    "acl":                          True,
    "delete":                       True,
    "location":                     True,
    "logging":                      True,
    "notification":                 True,
    "partNumber":                   True,
    "policy":                       True,
    "requestPayment":               True,
    "torrent":                      True,
    "uploadId":                     True,
    "uploads":                      True,
    "versionId":                    True,
    "versioning":                   True,
    "versions":                     True,
    "response-content-type":        True,
    "response-content-language":    True,
    "response-expires":             True,
    "response-cache-control":       True,
    "response-content-disposition": True,
    "response-content-encoding":    True
}




# Note: this function just for oss_upload
def sendrequest_v2(method, restful_server, urlpath, http_headers, parameters, body):

    # process the params
    if parameters is not None:
        i = 0
        for k in parameters.keys():
            if i == 0:
                urlpath += '?'
            else:
                urlpath += '&'
            i = i+1
            urlpath += str(k) + '=' + str(parameters[k])


    convert_httpheaders = {}
    for k in http_headers.iterkeys():
        v = http_headers.get(k)
        convert_httpheaders[k] = v[0]



    print 'urlpath=%s' % urlpath
    print convert_httpheaders
    print 'body=%s' % body


    conn = httplib.HTTPConnection(restful_server)
    conn.request(method, urlpath, body, convert_httpheaders)

    response = conn.getresponse()
    resp_body = response.read()
    resp_status = response.status
    resp_headers = response.getheaders()
    conn.close()

    #return resp_status, resp_headers, resp_body

    if resp_status == 200:
        dict_rsp = {}
        etag = response.getheader("etag", None)

        if etag == None:
            dict_rsp['code'] = -1
            dict_rsp['msg'] = 'no etag'
        else:
            dict_rsp['code'] = 0
            dict_rsp['msg'] = 'successful'
            dict_rsp['etag'] = eval(etag)
        return dict_rsp
    else:
        dict_error = {}
        dict_error['code'] = -1
        dict_error['httpcode'] = resp_status
        dict_error['msg'] = "system error"
        return dict_error

# Note: this function just for oss_upload
def sendrequest_v3(method, restful_server, urlpath, http_headers, parameters, body):
    # process the params
    if parameters is not None:
        i = 0
        for k in parameters.keys():
            if i == 0:
                urlpath += '?'
            else:
                urlpath += '&'
            i = i + 1
            urlpath += str(k) + '=' + str(parameters[k])

    convert_httpheaders = {}
    for k in http_headers.iterkeys():
        v = http_headers.get(k)
        convert_httpheaders[k] = v[0]

    print 'urlpath=%s' % urlpath
    print convert_httpheaders
    print 'body=%s' % body

    conn = httplib.HTTPConnection(restful_server)
    conn.request(method, urlpath, body, convert_httpheaders)

    response = conn.getresponse()
    resp_body = response.read()
    resp_status = response.status
    resp_headers = response.getheaders()
    conn.close()

    if resp_status == 200:
        dict_rsp = {}
        dict_rsp['code'] = 0
        dict_rsp['data'] = resp_body
        return dict_rsp
    else:
        dict_error={}
        dict_error['code'] = -1
        dict_error['httpcode'] = resp_status
        dict_error['msg'] = "system error"
        dict_error['data'] = resp_body
        return dict_error


# Note: this function just for oss_upload
def sendrequest_v4(method, restful_server, urlpath, http_headers, parameters, body):
    # process the params
    if parameters is not None:
        i = 0
        for k in parameters.keys():
            if i == 0:
                urlpath += '?'
            else:
                urlpath += '&'
            i = i + 1
            urlpath += str(k) + '=' + str(parameters[k])

    convert_httpheaders = {}
    for k in http_headers.iterkeys():
        v = http_headers.get(k)
        convert_httpheaders[k] = v[0]

    print 'urlpath=%s' % urlpath
    print convert_httpheaders
    print 'body=%s' % body

    conn = httplib.HTTPConnection(restful_server)
    conn.request(method, urlpath, body, convert_httpheaders)

    response = conn.getresponse()
    resp_body = response.read()
    resp_status = response.status
    resp_headers = response.getheaders()
    conn.close()

    if resp_status == 200:
        dict_rsp = {}
        dict_rsp['code'] = 0
        dict_rsp['file-length'] = resp_headers
        return dict_rsp
    else:
        dict_error={}
        dict_error['code'] = -1
        dict_error['httpcode'] = resp_status
        dict_error['msg'] = "system error"
        dict_error['data'] = resp_body
        return dict_error


class s3client:
    def __init__(self, restful_server, access_key, secret_key):
        self.restful_server = restful_server
        self.access_key = access_key
        self.secret_key = secret_key



    # Note1: here http_headers is a directory like: http_headers["header-value"] = [value1, value2]
    # Note2: the canonical_path may need urlencoded(here we don't process it)
    # Note3: the canonical_path should be canonical(how to canonical? call AmazonEscape()) 
    def sign(self, method, canonical_path, parameters, http_headers):
        if self.secret_key == "":
            return  # no auth secret; skip signing, e.g. for public read-only buckets

        md5 = ""
        ctype = ""
        xamzdate = False
        date = ""
        xamz_elems = []


        for k in http_headers.iterkeys():
            v = http_headers.get(k)
            k = k.lower()

            if k == "content-md5":
                md5 = v[0]
            elif k == "content-type":
                ctype = v[0]
            elif k == "date":
                if xamzdate == False:
                    date = v[0]
            else:
                if k.startswith("x-amz-",0,len(k)):
                    str = k + ":"
                    for i, val in enumerate(v):
                        str += val
                        if i != len(v) -1:
                            str += ","

                    xamz_elems.append(str)
                    if k == "x-amz-date":
                        xamzdate = True
                        date = ""

        xamz = ""
        if len(xamz_elems) > 0:
            xamz_elems.sort()
            # print xamz_elems
            for elem in xamz_elems:
                xamz += (elem + "\n")

        # print xamz

        expires = False
        if parameters.has_key("Expires"):
            # Query string request authentication alternative
            expires = True
            date = parameters["Expires"]
            parameters["AWSAccessKeyId"] = self.access_key


        # process the parameters
        signParams = []
        for k, v in parameters.iteritems():
            if s3ParamsToSign.has_key(k) and s3ParamsToSign[k] == True:
                if v == "":
                    signParams.append(k)
                else:
                    signParams.append(k + "=" + v)

        #print signParams
        if len(signParams):
            signParams.sort()
            canonical_path = canonical_path + "?"
            for i, v in enumerate(signParams):
                canonical_path += v
                if i != len(signParams) -1:
                    canonical_path += "&"

        print canonical_path


        payload = method + "\n" + md5 + "\n" + ctype + "\n" + date + "\n" + xamz + canonical_path
        print "payload: %s" %payload

        #byte_sk = bytearray(self.secret_key, "utf-8")

        #hmac_sha = hmac.new(byte_sk, digestmod=hashlib.sha1)
        hmac_sha = hmac.new(self.secret_key, digestmod=hashlib.sha1)
        hmac_sha.update(payload)
        signature = base64.b64encode(hmac_sha.digest())


        print "signature: %s" % signature



        if expires:
            parameters["Signature"] = signature
        else:
            str = "AWS " + self.access_key + ":" + signature
            http_headers["Authorization"] = [str]    #["AWS " + self.access_key + ":" + signature]



    def multiUploadPart(self, bucket_name, object_name, part_number, upload_id, byte_data):
        canonical_path = "/" + bucket_name + "/" + object_name

        parameters = {}
        parameters["uploadId"] = upload_id
        parameters["partNumber"] = part_number

        http_headers = {}
        http_headers["Content-Type"] = ["binary/octet-stream"]
        http_headers["Content-Length"] = [len(byte_data)]

        hash_md5 = hashlib.md5(byte_data)
        digest = hash_md5.digest()
        md5b64 = base64.standard_b64encode(digest)
        http_headers["Content-MD5"] = [md5b64]

        GMT_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
        http_headers["x-amz-date"] = [datetime.datetime.utcnow().strftime(GMT_FORMAT)]

        self.sign("PUT", canonical_path, parameters,http_headers)

        print "canonical_path: %s" % canonical_path
        print "parameters: %s" % parameters
        print "http_headers", http_headers

        resp = sendrequest_v2("PUT", self.restful_server, canonical_path, http_headers,parameters, byte_data)
        print resp
        return resp


    def simpleUpload(self, bucket_name, object_name, acl, byte_data):
        canonical_path = "/" + bucket_name + "/" + object_name

        parameters = {}


        http_headers = {}
        http_headers["Content-Type"] = ["binary/octet-stream"]
        http_headers["Content-Length"] = [len(byte_data)]

        hash_md5 = hashlib.md5(byte_data)
        digest = hash_md5.digest()
        md5b64 = base64.standard_b64encode(digest)
        http_headers["Content-MD5"] = [md5b64]

        GMT_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
        http_headers["x-amz-date"] = [datetime.datetime.utcnow().strftime(GMT_FORMAT)]

        #"public-read"     # here default private     # if bucket is private, the object should be private
        #http_headers["x-amz-acl"] = ["public-read"]
        http_headers["x-amz-acl"] = [acl]

        self.sign("PUT", canonical_path, parameters, http_headers)

        print "canonical_path: %s" % canonical_path
        print "parameters: %s" % parameters
        print "http_headers", http_headers

        resp = sendrequest_v3("PUT", self.restful_server, canonical_path, http_headers, {}, byte_data)
        print resp

        return resp


    def simpleDownload(self, bucket_name, object_name):
        canonical_path = "/" + bucket_name + "/" + object_name

        http_headers = {}
        http_headers["Content-Length"] = [0]
        http_headers["Accept-Encoding"] = ["gzip"]
        http_headers["Range"] = ["bytes=20-24"]
        #http_headers["range"] = ["Range:bytes=0-20"]
        #http_headers["range"] = ["Range:bytes=56099557-56099800"]

        GMT_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
        http_headers["x-amz-date"] = [datetime.datetime.utcnow().strftime(GMT_FORMAT)]

        self.sign("GET", canonical_path,{},http_headers)
        print "canonical_path: %s" % canonical_path
        print "http_headers: %s", http_headers

        resp = sendrequest_v3("GET", self.restful_server, canonical_path, http_headers, {}, None)
        print resp

        return resp

    # expiry must smaller than 30min
    def generateDownloadURL(self, bucket_name, object_name, expiry):
        canonical_path = "/" + bucket_name + "/" + object_name
        http_headers = {}

        print "now:" , time.time()
        # expires = datetime.datetime.utcnow() + expiry
        expires = int(time.time()) + expiry
        http_parameters = {"Expires" : str(expires)}

        self.sign("GET", canonical_path, http_parameters, http_headers)
        signedURL = self.restful_server + canonical_path

        i = 0
        for k, v in http_parameters.iteritems():
            if i == 0:
                signedURL += "?" + k + "=" + v
            else:
                signedURL += "&" + k + "=" + v
            i = i+1

        print "generated downloadURL: %s" % signedURL


    def getContentLength(self, bucket_name, object_name):
        canonical_path = "/" + bucket_name + "/" + object_name

        http_headers = {}
        http_headers["Content-Length"] = [0]

        GMT_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
        http_headers["x-amz-date"] = [datetime.datetime.utcnow().strftime(GMT_FORMAT)]

        self.sign("HEAD", canonical_path, {}, http_headers)
        print "canonical_path: %s" % canonical_path
        print "http_headers: %s", http_headers

        resp = sendrequest_v4("HEAD", self.restful_server, canonical_path, http_headers, {}, None)
        print resp

        return resp

    def removeObject(self, bucket_name, object_name):
        canonical_path = "/" + bucket_name + "/" + object_name
        http_headers = {}
        #http_headers["Content-Length"] = [0]

        GMT_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
        http_headers["x-amz-date"] = [datetime.datetime.utcnow().strftime(GMT_FORMAT)]

        self.sign("DELETE", canonical_path, {}, http_headers)
        print "canonical_path: %s" % canonical_path
        print "http_headers: %s", http_headers

        resp = sendrequest_v3("DELETE", self.restful_server, canonical_path, http_headers, {}, None)

        print resp
        return resp

测试s3_test.py:

# -*- coding:utf-8 -*-

import s3_client
import hashlib
import datetime
import base64
import urllib


def testSimpleUpload():
    UPLOAD_SERVER = "192.168.1.211:7480"
    access_key = "6B6QT7VRNKGIGB712ED8"
    secret_key = "NVWLz0Q9DPvBZZIBFq7ORLGRLwg3k1hmZoTXMeIo"

    client = s3_client.s3client(UPLOAD_SERVER, access_key, secret_key)
    str = "hello,world"
    data = bytearray(str, "utf-8")

    object_name = "helloworld_7"
    bucket_name = "ivan_test"
    client.simpleUpload(bucket_name, object_name, "private", data)

def testGenerateDownloadURL():
    DOWNLOAD_SERVER = "192.168.1.211:7480"
    access_key = "6B6QT7VRNKGIGB712ED8"
    secret_key = "NVWLz0Q9DPvBZZIBFq7ORLGRLwg3k1hmZoTXMeIo"
    bucket_name = "ivan_test"
    object_name = "helloworld_7"
    expiry = 1800

    client = s3_client.s3client(DOWNLOAD_SERVER, access_key, secret_key)

    client.generateDownloadURL(bucket_name, object_name, expiry)


if __name__ == "__main__":

    handler = "generateDownloadURL"


    if handler == "generateDownloadURL":
        testGenerateDownloadURL()
    else:
        print "unsupported handler '%s'" % handler

关于如何对path进行canonical,如下我们给出部分示例(go 版本):

// amazonShouldEscape returns true if byte should be escaped
func amazonShouldEscape(c byte) bool {
	return !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
		(c >= '0' && c <= '9') || c == '_' || c == '-' || c == '~' || c == '.' || c == '/' || c == ':')
}

func AmazonEscape(s string) string {
	hexCount := 0

	for i := 0; i < len(s); i++ {
		if amazonShouldEscape(s[i]) {
			hexCount++
		}
	}

	if hexCount == 0 {
		return s
	}

	t := make([]byte, len(s)+2*hexCount)
	j := 0
	for i := 0; i < len(s); i++ {
		if c := s[i]; amazonShouldEscape(c) {
			t[j] = '%'
			t[j+1] = "0123456789ABCDEF"[c>>4]
			t[j+2] = "0123456789ABCDEF"[c&15]
			j += 3
		} else {
			t[j] = s[i]
			j++
		}
	}
	return string(t)
}



[参看]

  1. boto3 github

  2. s3 python

  3. ceph对象存储(rgw)服务、高可用安装配置

  4. s3接口认证说明

  5. S3 用户手册

  6. Amazon S3

  7. Amazon SDK

  8. CEPH 对象存储的系统池介绍