]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw: add rate limit for LIST & DELETE ops 64762/head
authorMark Kogan <mkogan@ibm.com>
Wed, 30 Jul 2025 12:54:19 +0000 (12:54 +0000)
committerMark Kogan <mkogan@ibm.com>
Tue, 16 Sep 2025 09:55:15 +0000 (09:55 +0000)
Add rate limiting specific to LIST ops,
similar to the current rate-limiting
(https://docs.ceph.com/en/latest/radosgw/admin/#rate-limit-management)

Example usage:

```
./bin/radosgw-admin ratelimit set --ratelimit-scope=user --uid=<UID> --max_list_ops=2
./bin/radosgw-admin ratelimit set --ratelimit-scope=user --uid=<UID> --max_delete_ops=2
./bin/radosgw-admin ratelimit enable --ratelimit-scope=user --uid=<UID>

./bin/radosgw-admin ratelimit get --ratelimit-scope=user --uid=<UID>
{
  "user_ratelimit": {
    "max_read_ops": 0,
    "max_write_ops": 0,
    "max_list_ops": 2,
    "max_delete_ops": 2,
    "max_read_bytes": 0,
    "max_write_bytes": 0,
    "enabled": true
  }
}

pkill -9 radosgw
./bin/radosgw -c ./ceph.conf ...

aws --endpoint-url 'http://0:8000' s3 mb s3://bkt
aws --endpoint-url 'http://0:8000' s3 cp  ./ceph.conf s3://bkt

aws --endpoint-url http://0:8000 s3api list-objects-v2 --bucket bkt --prefix 'ceph.conf' --delimiter '/'
{
    "Contents": [
        {
            "Key": "ceph.conf",
            "LastModified": "2025-07-30T13:59:38+00:00",
            "ETag": "\"13d11d431ae290134562c019d9e40c0e\"",
            "Size": 32346,
            "StorageClass": "STANDARD"
        }
    ],
    "RequestCharged": null
}

aws --endpoint-url http://0:8000 s3api list-objects-v2 --bucket bkt --prefix 'ceph.conf' --delimiter '/'
{
    "Contents": [
        {
            "Key": "ceph.conf",
            "LastModified": "2025-07-30T13:59:38+00:00",
            "ETag": "\"13d11d431ae290134562c019d9e40c0e\"",
            "Size": 32346,
            "StorageClass": "STANDARD"
        }
    ],
    "RequestCharged": null
}

aws --endpoint-url http://0:8000 s3api list-objects-v2 --bucket bkt --prefix 'ceph.conf' --delimiter '/'
argument of type 'NoneType' is not iterable

tail -F ./out/radosgw.8000.log | grep beast
...
beast: 0x7fffbbe09780:  [30/Jul/2025:15:44:50.359 +0000] " GET /bkt?list-type=2&delimiter=%2F&prefix=ceph.conf&encoding-type=url HTTP/1.1" 200 535 - "aws-cli/2.15.31 Python/3.9.21 Linux/5.14.0-570.28.1.el9_6.x86_64 source/x86_64.rhel.9 prompt/off command/s3api.list-objects-v2" - latency=0.000999995s
beast: 0x7fffbbe09780:  [30/Jul/2025:15:44:53.904 +0000] " GET /bkt?list-type=2&delimiter=%2F&prefix=ceph.conf&encoding-type=url HTTP/1.1" 200 535 - "aws-cli/2.15.31 Python/3.9.21 Linux/5.14.0-570.28.1.el9_6.x86_64 source/x86_64.rhel.9 prompt/off command/s3api.list-objects-v2" - latency=0.000999995s
                                                                                                                                           vvv
beast: 0x7fffbbe09780:  [30/Jul/2025:15:44:58.192 +0000] " GET /bkt?list-type=2&delimiter=%2F&prefix=ceph.conf&encoding-type=url HTTP/1.1" 503 228 - "aws-cli/2.15.31 Python/3.9.21 Linux/5.14.0-570.28.1.el9_6.x86_64 source/x86_64.rhel.9 prompt/off command/s3api.list-objects-v2" - latency=0.000000000s
beast: 0x7fffbbe09780:  [30/Jul/2025:15:44:58.798 +0000] " GET /bkt?list-type=2&delimiter=%2F&prefix=ceph.conf&encoding-type=url HTTP/1.1" 503 228 - "aws-cli/2.15.31 Python/3.9.21 Linux/5.14.0-570.28.1.el9_6.x86_64 source/x86_64.rhel.9 prompt/off command/s3api.list-objects-v2" - latency=0.000999994s
beast: 0x7fffbbe09780:  [30/Jul/2025:15:44:59.807 +0000] " GET /bkt?list-type=2&delimiter=%2F&prefix=ceph.conf&encoding-type=url HTTP/1.1" 503 228 - "aws-cli/2.15.31 Python/3.9.21 Linux/5.14.0-570.28.1.el9_6.x86_64 source/x86_64.rhel.9 prompt/off command/s3api.list-objects-v2" - latency=0.000000000s

s3cmd put ./ceph.conf s3://bkt/1
s3cmd put ./ceph.conf s3://bkt/2
s3cmd put ./ceph.conf s3://bkt/3

s3cmd rm s3://bkt/1
s3cmd rm s3://bkt/2
s3cmd rm s3://bkt/3

delete: 's3://bkt/1'
delete: 's3://bkt/2'
WARNING: Retrying failed request: /3 (503 (SlowDown))
WARNING: Waiting 3 sec...
WARNING: Retrying failed request: /3 (503 (SlowDown))
                                      ^^^
```

Fixes: https://tracker.ceph.com/issues/72894
Signed-off-by: Mark Kogan <mkogan@ibm.com>
Update PendingReleaseNotes

Co-authored-by: Yuval Lifshitz <yuvalif@yahoo.com>
Signed-off-by: Mark Kogan <31659604+mkogan1@users.noreply.github.com>
Update PendingReleaseNotes

Co-authored-by: Yuval Lifshitz <yuvalif@yahoo.com>
Signed-off-by: Mark Kogan <31659604+mkogan1@users.noreply.github.com>
15 files changed:
PendingReleaseNotes
doc/radosgw/admin.rst
doc/radosgw/adminops.rst
qa/tasks/radosgw_admin_rest.py
src/common/options/rgw.yaml.in
src/rgw/radosgw-admin/radosgw-admin.cc
src/rgw/rgw_common.cc
src/rgw/rgw_common.h
src/rgw/rgw_process.cc
src/rgw/rgw_ratelimit.h
src/rgw/rgw_rest_ratelimit.cc
src/test/cli/radosgw-admin/help.t
src/test/rgw/bench_rgw_ratelimit.cc
src/test/rgw/bench_rgw_ratelimit_gc.cc
src/test/rgw/test_rgw_ratelimit.cc

index 7b15dad69e6d778ef70414b31fe72745a31d57d3..139af86c4a8a52d2571cb27bd36edaf435ef0d64 100644 (file)
@@ -583,6 +583,13 @@ CephFS: Disallow delegating preallocated inode ranges to clients. Config
 * RGW: The serialized format of notification and topics has changed, so that 
   new/updated topics will be unreadable by old RGWs. We recommend completing 
   the RGW upgrades before creating or modifying any notification topics.
+* RGW: New rate limiting options have been added to control the number of
+  bucket listing requests (`radosgw-admin ratelimit set ... --max-list-ops=`)
+  and delete operations (`radosgw-admin ratelimit set ... --max-delete-ops=`)
+  per accumulation interval, along with the ability to configure the
+  accumulation interval duration (`rgw_ratelimit_interval`).
+  Their default value is `0`, which keeps backward compatibility to the
+  previous behavior of READ and WRITE operations rate limiting.
 * RBD: Trailing newline in passphrase files (`<passphrase-file>` argument in
   `rbd encryption format` command and `--encryption-passphrase-file` option
   in other commands) is no longer stripped.
index 2740a287a11cd2946b9503aebbeb7b7bae1350ab..c62a3cdc43508f88a817e7f2f0c8d2118ff92e31 100644 (file)
@@ -622,9 +622,21 @@ Rate Limit Management
 =====================
 
 Quotas can be set for The Ceph Object Gateway on users and buckets. The "rate
-limit" includes the maximum number of read operations (read ops) and write
-operations (write ops) per minute as well as the number of bytes per minute
-that can be written or read per user or per bucket.
+limit" includes the maximum number of read operations and write operations
+per accumulation interval as well as the number of bytes per accumulation interval
+that can be written or read per user or per bucket. It also includes the maximum
+number of list requests and delete operations per accumulation interval.
+The accumulation interval is configured by the :confval:`rgw_ratelimit_interval` option.
+The default value is 60 seconds.
+(Note: S3 Multi-Object Delete operation are currently not supported by rate limiting)
+
+The configured limits should be divided by the number of active object gateways. For example,
+if "user A" is to be be limited to 10 ops per minute and there are two object gateways in the cluster,
+then the limit on "user A" should be 5 (10 ops per minute / 2 RGWs).
+If the requests are not balanced between RGWs, the rate limit might be underutilized.
+For example: if the ops limit is 5 and there are two RGWs,
+but the Load Balancer sends load to only one of those RGWs,
+the effective limit is 5 ops, because this limit is enforced per RGW.
 
 Read Requests and Write Requests
 --------------------------------
@@ -636,8 +648,8 @@ How Metrics Work
 Each object gateway tracks per-user metrics separately from bucket metrics.
 These metrics are not shared with other gateways. The configured limits should
 be divided by the number of active object gateways. For example, if "user A" is
-to be be limited to 10 ops per minute and there are two object gateways in the
-cluster, then the limit on "user A" should be ``5`` (10 ops per minute / 2
+to be be limited to 10 ops per accumulation interval and there are two object gateways in the
+cluster, then the limit on "user A" should be ``5`` (10 ops per accumulation interval / 2
 RGWs). If the requests are **not** balanced between RGWs, the rate limit might
 be underutilized. For example: if the ops limit is ``5`` and there are two
 RGWs, **but** the Load Balancer sends load to only one of those RGWs, the
@@ -654,10 +666,10 @@ rate limit is reached during the execution of the request. The RGW keeps track
 of a "debt" consisting of bytes used in excess of the configured value; users
 or buckets that incur this kind of debt are prevented from sending more
 requests until the "debt" has been repaid. The maximum size of the "debt" is
-twice the max-read/write-bytes per minute. If "user A" is subject to a 1-byte
-read limit per minute and they attempt to ``GET`` an object that is 1 GB in size,
+twice the max-read/write-bytes per accumulation interval. If "user A" is subject to a 1-byte
+read limit per accumulation interval and they attempt to ``GET`` an object that is 1 GB in size,
 then the ``GET`` action will fail. After "user A" has completed this 1 GB
-operation, RGW blocks the user's requests for up to two minutes. After this
+operation, RGW blocks the user's requests for up to two accumulation intervals. After this
 time has elapsed, "user A" will be able to send ``GET`` requests again.
 
 
@@ -668,19 +680,27 @@ time has elapsed, "user A" will be able to send ``GET`` requests again.
   user.
 
 - **Maximum Read Ops:** The ``--max-read-ops`` setting allows you to limit read
-  bytes per minute per RGW instance. A ``0`` value disables throttling. 
+  bytes per accumulation interval per RGW instance. A ``0`` value disables throttling.
   
 - **Maximum Read Bytes:** The ``--max-read-bytes`` setting allows you to limit
-  read bytes per minute per RGW instance. A ``0`` value disables throttling. 
+  read bytes per accumulation interval per RGW instance. A ``0`` value disables throttling.
 
 - **Maximum Write Ops:** The ``--max-write-ops`` setting allows you to specify
-  the maximum number of write ops per minute per RGW instance. A ``0`` value
+  the maximum number of write ops per accumulation interval per RGW instance. A ``0`` value
   disables throttling.
   
 - **Maximum Write Bytes:** The ``--max-write-bytes`` setting allows you to
-  specify the maximum number of write bytes per minute per RGW instance. A
+  specify the maximum number of write bytes per accumulation interval per RGW instance. A
   ``0`` value disables throttling.
+
+- **Maximum List Ops:** The ``--max-list-ops`` setting allows you to
+  specify the maximum number of bucket listing requests per accumulation interval per RGW instance.
+  A ``0`` value disables throttling.
+
+- **Maximum Delete Ops:** The ``--max-delete-ops`` setting allows you to
+  specify the maximum number of delete operations per accumulation interval per RGW instance.
+  A ``0`` value disables throttling.
+
 - **Rate Limit Scope:** The ``--ratelimit-scope`` option sets the scope for the
   rate limit.  The options are ``bucket`` , ``user`` and ``anonymous``. Bucket
   rate limit apply to buckets.  The user rate limit applies to a user.  The
@@ -699,7 +719,8 @@ parameters:
 
    radosgw-admin ratelimit set --ratelimit-scope=user --uid=<uid> \
                                  <[--max-read-ops=<num ops>] [--max-read-bytes=<num bytes>] \
-                                 [--max-write-ops=<num ops>] [--max-write-bytes=<num bytes>]>
+                                 [--max-write-ops=<num ops>] [--max-write-bytes=<num bytes>] \
+                                 [--max-list-ops=<num ops>] [--max-delete-ops=<num ops>]>
 
 An example of using ``radosgw-admin ratelimit set`` to set a rate limit might
 look like this: 
@@ -764,7 +785,8 @@ The following is the general form of commands that set rate limit parameters:
 
    radosgw-admin ratelimit set --ratelimit-scope=bucket --bucket=<bucket> \
                                  <[--max-read-ops=<num ops>] [--max-read-bytes=<num bytes>] \
-                                 [--max-write-ops=<num ops>] [--max-write-bytes=<num bytes>]>
+                                 [--max-write-ops=<num ops>] [--max-write-bytes=<num bytes>] \
+                                 [--max-list-ops=<num ops>] [--max-delete-ops=<num ops>]>
 
 An example of using ``radosgw-admin ratelimit set`` to set a rate limit for a
 bucket might look like this: 
index 1c643fea8d94deb56453b1d05311c90eafac1c02..fb1903efa395d2fea230389067e783c8cf1f167b 100644 (file)
@@ -2072,8 +2072,9 @@ as mentioned in Set Bucket Quota section above.
 Rate Limit
 ==========
 
-The Admin Operations API enables you to set and get ratelimit configurations on users and on bucket and global rate limit configurations. See `Rate Limit Management`_ for additional details. 
-Rate Limit includes the maximum number of operations and/or bytes per minute, separated by read and/or write, to a bucket and/or by a user and the maximum storage size in megabytes.
+The Admin Operations API enables you to set and get ratelimit configurations on users and on bucket and global rate limit configurations. See `Rate Limit Management`_ for additional details.
+Rate Limit includes the maximum number of operations and/or bytes per accumulation interval, separated by read and/or write (Additionally list and get operations),
+to a bucket and/or by a user and the maximum storage size in megabytes.
 
 To view rate limit, the user must have a ``ratelimit=read`` capability. To set,
 modify or disable a ratelimit, the user must have ``ratelimit=write`` capability.
@@ -2087,16 +2088,22 @@ Valid parameters for quotas include:
 - **User:** The ``uid`` option allows you to specify a rate limit for a user.
 
 - **Maximum Read Bytes:** The ``max-read-bytes`` setting allows you to specify
-  the maximum number of read bytes per minute. A 0 value disables this setting.
+  the maximum number of read bytes per accumulation interval. A 0 value disables this setting.
 
 - **Maximum Write Bytes:** The ``max-write-bytes`` setting allows you to specify
-  the maximum number of write bytes per minute. A 0 value disables this setting.
+  the maximum number of write bytes per accumulation interval. A 0 value disables this setting.
 
 - **Maximum Read Ops:** The ``max-read-ops`` setting allows you to specify
-  the maximum number of read ops per minute. A 0 value disables this setting.
+  the maximum number of read ops per accumulation interval. A 0 value disables this setting.
 
 - **Maximum Write Ops:** The ``max-write-ops`` setting allows you to specify
-  the maximum number of write ops per minute. A 0 value disables this setting.
+  the maximum number of write ops per accumulation interval. A 0 value disables this setting.
+
+- **Maximum List Ops:** The ``max-list-ops`` setting allows you to specify
+  the maximum number of bucket listing requests per accumulation interval. A 0 value disables this setting.
+
+- **Maximum Delete Ops:** The ``max-delete-ops`` setting allows you to specify
+  the maximum number of delete operations per accumulation interval. A 0 value disables throttling.
 
 - **Global:** The ``global`` option allows you to specify a global rate limit.
   The value should be either 'True' or 'False'.
@@ -2123,7 +2130,7 @@ Set User Rate Limit
 To set a rate limit, the user must have ``ratelimit`` capability set with ``write``
 permission. ::
 
-       POST /{admin}/ratelimit?ratelimit-scope=user&uid=<uid><[&max-read-bytes=<bytes>][&max-write-bytes=<bytes>][&max-read-ops=<ops>][&max-write-ops=<ops>][enabled=<True|False>]>
+       POST /{admin}/ratelimit?ratelimit-scope=user&uid=<uid><[&max-read-bytes=<bytes>][&max-write-bytes=<bytes>][&max-read-ops=<ops>][&max-write-ops=<ops>][&max-list-ops=<ops>][&max-delete-ops=<ops>][&enabled=<True|False>]>
 
 
 
@@ -2143,7 +2150,7 @@ Set Rate Limit for an Individual Bucket
 To set a rate limit, the user must have ``ratelimit`` capability set with ``write``
 permission. ::
 
-       POST /{admin}/ratelimit?bucket=<bucket-name>&ratelimit-scope=bucket<[&max-read-bytes=<bytes>][&max-write-bytes=<bytes>][&max-read-ops=<ops>][&max-write-ops=<ops>]>
+       POST /{admin}/ratelimit?bucket=<bucket-name>&ratelimit-scope=bucket<[&max-read-bytes=<bytes>][&max-write-bytes=<bytes>][&max-read-ops=<ops>][&max-write-ops=<ops>][&max-list-ops=<ops>][&max-delete-ops=<ops>][&enabled=<True|False>]>
 
 
 
@@ -2163,7 +2170,7 @@ Set Global User Rate Limit
 To set a rate limit, the user must have ``ratelimit`` capability set with ``write``
 permission. ::
 
-       POST /{admin}/ratelimit?ratelimit-scope=user&global=<True|False><[&max-read-bytes=<bytes>][&max-write-bytes=<bytes>][&max-read-ops=<ops>][&max-write-ops=<ops>][enabled=<True|False>]>
+       POST /{admin}/ratelimit?ratelimit-scope=user&global=<True|False><[&max-read-bytes=<bytes>][&max-write-bytes=<bytes>][&max-read-ops=<ops>][&max-write-ops=<ops>][&max-list-ops=<ops>][&max-delete-ops=<ops>][&enabled=<True|False>]>
 
 
 
@@ -2173,7 +2180,7 @@ Set Global Rate Limit Bucket
 To set a rate limit, the user must have ``ratelimit`` capability set with ``write``
 permission. ::
 
-       POST /{admin}/ratelimit?ratelimit-scope=bucket&global=<True|False><[&max-read-bytes=<bytes>][&max-write-bytes=<bytes>][&max-read-ops=<ops>][&max-write-ops=<ops>]>
+       POST /{admin}/ratelimit?ratelimit-scope=bucket&global=<True|False><[&max-read-bytes=<bytes>][&max-write-bytes=<bytes>][&max-read-ops=<ops>][&max-write-ops=<ops>][&max-list-ops=<ops>][&max-delete-ops=<ops>][&enabled=<True|False>]>
 
 
 
@@ -2183,7 +2190,7 @@ Set Global Anonymous User Rate Limit
 To set a rate limit, the user must have ``ratelimit`` capability set with ``write``
 permission. ::
 
-       POST /{admin}/ratelimit?ratelimit-scope=anon&global=<True|False><[&max-read-bytes=<bytes>][&max-write-bytes=<bytes>][&max-read-ops=<ops>][&max-write-ops=<ops>][enabled=<True|False>]>
+       POST /{admin}/ratelimit?ratelimit-scope=anon&global=<True|False><[&max-read-bytes=<bytes>][&max-write-bytes=<bytes>][&max-read-ops=<ops>][&max-write-ops=<ops>][&max-list-ops=<ops>][&max-delete-ops=<ops>][&enabled=<True|False>]>
 
 
 
index 049a7903b1cc8b0537645ec8de7cf2cea415157a..19c770ea301a738db3543224988a03e01f5d4cb8 100644 (file)
@@ -982,3 +982,74 @@ def task(ctx, config):
     # TESTCASE 'ratelimit' 'global' 'modify' 'anonymous' 'enabled' 'succeeds'
     (ret, out) = rgwadmin_rest(admin_conn, ['ratelimit', 'modify'], {'ratelimit-scope' : 'bucket', 'global': 'true', 'enabled' : 'true'})
     assert ret == 200
+
+    # TESTCASE 'ratelimit' 'user' 'modify' 'max-list-ops = 100' 'succeeds'
+    (ret, out) = rgwadmin_rest(admin_conn, ['ratelimit', 'modify'], {'ratelimit-scope' : 'user', 'uid' : ratelimit_user, 'max-list-ops' : '100'})
+    assert ret == 200
+    (ret, out) = rgwadmin_rest(admin_conn, ['ratelimit', 'info'], {'ratelimit-scope' : 'user', 'uid' : ratelimit_user})
+    assert ret == 200
+    user_ratelimit = out['user_ratelimit']
+    assert user_ratelimit['max_list_ops'] == 100
+
+    # TESTCASE 'ratelimit' 'user' 'modify' 'max-delete-ops = 50' 'succeeds'
+    (ret, out) = rgwadmin_rest(admin_conn, ['ratelimit', 'modify'], {'ratelimit-scope' : 'user', 'uid' : ratelimit_user, 'max-delete-ops' : '50'})
+    assert ret == 200
+    (ret, out) = rgwadmin_rest(admin_conn, ['ratelimit', 'info'], {'ratelimit-scope' : 'user', 'uid' : ratelimit_user})
+    assert ret == 200
+    user_ratelimit = out['user_ratelimit']
+    assert user_ratelimit['max_delete_ops'] == 50
+
+    # TESTCASE 'ratelimit' 'bucket' 'modify' 'max-list-ops = 200' 'succeeds'
+    (ret, out) = rgwadmin_rest(admin_conn, ['ratelimit', 'modify'], {'ratelimit-scope' : 'bucket', 'bucket' : ratelimit_bucket, 'max-list-ops' : '200'})
+    assert ret == 200
+    (ret, out) = rgwadmin_rest(admin_conn, ['ratelimit', 'info'], {'ratelimit-scope' : 'bucket', 'bucket' : ratelimit_bucket})
+    assert ret == 200
+    bucket_ratelimit = out['bucket_ratelimit']
+    assert bucket_ratelimit['max_list_ops'] == 200
+
+    # TESTCASE 'ratelimit' 'bucket' 'modify' 'max-delete-ops = 75' 'succeeds'
+    (ret, out) = rgwadmin_rest(admin_conn, ['ratelimit', 'modify'], {'ratelimit-scope' : 'bucket', 'bucket' : ratelimit_bucket, 'max-delete-ops' : '75'})
+    assert ret == 200
+    (ret, out) = rgwadmin_rest(admin_conn, ['ratelimit', 'info'], {'ratelimit-scope' : 'bucket', 'bucket' : ratelimit_bucket})
+    assert ret == 200
+    bucket_ratelimit = out['bucket_ratelimit']
+    assert bucket_ratelimit['max_delete_ops'] == 75
+
+    # TESTCASE 'ratelimit' 'global' 'modify' 'bucket' 'max-list-ops = 500' 'succeeds'
+    (ret, out) = rgwadmin_rest(admin_conn, ['ratelimit', 'modify'], {'ratelimit-scope' : 'bucket', 'global': 'true', 'max-list-ops' : '500'})
+    assert ret == 200
+    (ret, out) = rgwadmin_rest(admin_conn, ['ratelimit', 'info'], {'global' : 'true'})
+    assert ret == 200
+    # Check that global bucket ratelimit has the list ops set
+    assert 'bucket_ratelimit' in out
+    assert out['bucket_ratelimit']['max_list_ops'] == 500
+
+    # TESTCASE 'ratelimit' 'global' 'modify' 'bucket' 'max-delete-ops = 300' 'succeeds'
+    (ret, out) = rgwadmin_rest(admin_conn, ['ratelimit', 'modify'], {'ratelimit-scope' : 'bucket', 'global': 'true', 'max-delete-ops' : '300'})
+    assert ret == 200
+    (ret, out) = rgwadmin_rest(admin_conn, ['ratelimit', 'info'], {'global' : 'true'})
+    assert ret == 200
+    # Check that global bucket ratelimit has the delete ops set
+    assert 'bucket_ratelimit' in out
+    assert out['bucket_ratelimit']['max_delete_ops'] == 300
+
+    # TESTCASE 'ratelimit' 'user' 'modify' 'multiple ops at once' 'succeeds'
+    (ret, out) = rgwadmin_rest(admin_conn, ['ratelimit', 'modify'], {
+        'ratelimit-scope' : 'user',
+        'uid' : ratelimit_user,
+        'max-read-ops' : '1000',
+        'max-write-ops' : '800',
+        'max-list-ops' : '600',
+        'max-delete-ops' : '400',
+        'enabled' : 'true'
+    })
+    assert ret == 200
+    (ret, out) = rgwadmin_rest(admin_conn, ['ratelimit', 'info'], {'ratelimit-scope' : 'user', 'uid' : ratelimit_user})
+    assert ret == 200
+    user_ratelimit = out['user_ratelimit']
+    assert user_ratelimit['enabled'] == True
+    assert user_ratelimit['max_read_ops'] == 1000
+    assert user_ratelimit['max_write_ops'] == 800
+    assert user_ratelimit['max_list_ops'] == 600
+    assert user_ratelimit['max_delete_ops'] == 400
+
index 83be490b704910b55f975ef49c194d385493b67e..bbb386325cd45d37da33efcc0809c15ed96468f6 100644 (file)
@@ -4498,6 +4498,20 @@ options:
   default: false
   services:
   - rgw
+- name: rgw_ratelimit_interval
+  type: uint
+  level: advanced
+  desc: Time window for rate limiting in seconds
+  long_desc: This option sets the time window for rate limiting accumulation in seconds.
+    Requests that exceed the configured rate limits within this time window will be rejected.
+    The default is a 60 second token bucket.
+  default: 60
+  min: 1
+  services:
+  - rgw
+  flags:
+  - startup
+  with_legacy: true
 - name: rgw_redis_connection_pool_size
   type: int
   level: basic
index 377665ef5b344849fcce213fa3fd904cbe4d5462..13d0614cfc43abb8b4d0f144db65f9fbefe94e37 100644 (file)
@@ -493,10 +493,12 @@ void usage()
   cout << "   --max-size                    specify max size (in B/K/M/G/T, negative value to disable)\n";
   cout << "   --quota-scope                 scope of quota (bucket, user, account)\n";
   cout << "\nRate limiting options:\n";
-  cout << "   --max-read-ops                specify max requests per minute for READ ops per RGW (GET and HEAD request methods), 0 means unlimited\n";
-  cout << "   --max-read-bytes              specify max bytes per minute for READ ops per RGW (GET and HEAD request methods), 0 means unlimited\n";
-  cout << "   --max-write-ops               specify max requests per minute for WRITE ops per RGW (Not GET or HEAD request methods), 0 means unlimited\n";
-  cout << "   --max-write-bytes             specify max bytes per minute for WRITE ops per RGW (Not GET or HEAD request methods), 0 means unlimited\n";
+  cout << "   --max-read-ops                specify max requests per accumulation interval for READ ops per RGW (GET and HEAD request methods), 0 means unlimited\n";
+  cout << "   --max-read-bytes              specify max bytes per accumulation interval for READ ops per RGW (GET and HEAD request methods), 0 means unlimited\n";
+  cout << "   --max-write-ops               specify max requests per accumulation interval for WRITE ops per RGW (Not GET or HEAD request methods), 0 means unlimited\n";
+  cout << "   --max-write-bytes             specify max bytes per accumulation interval for WRITE ops per RGW (Not GET or HEAD request methods), 0 means unlimited\n";
+  cout << "   --max-list-ops                specify max requests per accumulation interval for bucket listing requests per RGW, 0 means unlimited\n";
+  cout << "   --max-delete-ops              specify max requests per accumulation interval for DELETE ops per RGW (DELETE request methods), 0 means unlimited\n";
   cout << "   --ratelimit-scope             scope of rate limiting: bucket, user, anonymous\n";
   cout << "                                 anonymous can be configured only with global rate limit\n";
   cout << "\nOrphans search options:\n";
@@ -1411,9 +1413,9 @@ static bool dump_string(const char *field_name, bufferlist& bl, Formatter *f)
 }
 
 bool set_ratelimit_info(RGWRateLimitInfo& ratelimit, OPT opt_cmd, int64_t max_read_ops, int64_t max_write_ops,
-                    int64_t max_read_bytes, int64_t max_write_bytes,
-                    bool have_max_read_ops, bool have_max_write_ops,
-                    bool have_max_read_bytes, bool have_max_write_bytes)
+                    int64_t max_list_ops, int64_t max_delete_ops, int64_t max_read_bytes, int64_t max_write_bytes,
+                    bool have_max_read_ops, bool have_max_write_ops, bool have_max_list_ops,
+                    bool have_max_delete_ops, bool have_max_read_bytes, bool have_max_write_bytes)
 {
   bool ratelimit_configured = true;
   switch (opt_cmd) {
@@ -1437,6 +1439,18 @@ bool set_ratelimit_info(RGWRateLimitInfo& ratelimit, OPT opt_cmd, int64_t max_re
           ratelimit_configured = true;
         }
       }
+      if (have_max_list_ops) {
+        if (max_list_ops >= 0) {
+          ratelimit.max_list_ops = max_list_ops;
+          ratelimit_configured = true;
+        }
+      }
+      if (have_max_delete_ops) {
+        if (max_delete_ops >= 0) {
+          ratelimit.max_delete_ops = max_delete_ops;
+          ratelimit_configured = true;
+        }
+      }
       if (have_max_read_bytes) {
         if (max_read_bytes >= 0) {
           ratelimit.max_read_bytes = max_read_bytes;
@@ -1521,10 +1535,10 @@ int set_bucket_quota(rgw::sal::Driver* driver, OPT opt_cmd,
 
 int set_bucket_ratelimit(rgw::sal::Driver* driver, OPT opt_cmd,
                      const string& tenant_name, const string& bucket_name,
-                     int64_t max_read_ops, int64_t max_write_ops,
-                     int64_t max_read_bytes, int64_t max_write_bytes,
-                     bool have_max_read_ops, bool have_max_write_ops,
-                     bool have_max_read_bytes, bool have_max_write_bytes)
+                     int64_t max_read_ops, int64_t max_write_ops, int64_t max_list_ops,
+                     int64_t max_delete_ops, int64_t max_read_bytes, int64_t max_write_bytes,
+                     bool have_max_read_ops, bool have_max_write_ops, bool have_max_list_ops,
+                     bool have_max_delete_ops, bool have_max_read_bytes, bool have_max_write_bytes)
 {
   std::unique_ptr<rgw::sal::Bucket> bucket;
   int r = driver->load_bucket(dpp(), rgw_bucket(tenant_name, bucket_name),
@@ -1545,10 +1559,10 @@ int set_bucket_ratelimit(rgw::sal::Driver* driver, OPT opt_cmd,
       return -EIO;
     }
   }
-  bool ratelimit_configured = set_ratelimit_info(ratelimit_info, opt_cmd, max_read_ops, max_write_ops,
-                         max_read_bytes, max_write_bytes,
-                         have_max_read_ops, have_max_write_ops,
-                         have_max_read_bytes, have_max_write_bytes);
+  bool ratelimit_configured = set_ratelimit_info(ratelimit_info, opt_cmd, max_read_ops, max_write_ops, max_list_ops,
+                         max_delete_ops, max_read_bytes, max_write_bytes,
+                         have_max_read_ops, have_max_write_ops, have_max_list_ops,
+                         have_max_delete_ops, have_max_read_bytes, have_max_write_bytes);
   if (!ratelimit_configured) {
     ldpp_dout(dpp(), 0) << "ERROR: no rate limit values have been specified" << dendl;
     return -EINVAL;
@@ -1566,10 +1580,10 @@ int set_bucket_ratelimit(rgw::sal::Driver* driver, OPT opt_cmd,
 }
 
 int set_user_ratelimit(OPT opt_cmd, std::unique_ptr<rgw::sal::User>& user,
-                     int64_t max_read_ops, int64_t max_write_ops,
-                     int64_t max_read_bytes, int64_t max_write_bytes,
-                     bool have_max_read_ops, bool have_max_write_ops,
-                     bool have_max_read_bytes, bool have_max_write_bytes)
+                     int64_t max_read_ops, int64_t max_write_ops, int64_t max_list_ops,
+                     int64_t max_delete_ops, int64_t max_read_bytes, int64_t max_write_bytes,
+                     bool have_max_read_ops, bool have_max_write_ops, bool have_max_list_ops,
+                     bool have_max_delete_ops, bool have_max_read_bytes, bool have_max_write_bytes)
 {
   RGWRateLimitInfo ratelimit_info;
   user->load_user(dpp(), null_yield);
@@ -1584,10 +1598,10 @@ int set_user_ratelimit(OPT opt_cmd, std::unique_ptr<rgw::sal::User>& user,
       return -EIO;
     }
   }
-  bool ratelimit_configured = set_ratelimit_info(ratelimit_info, opt_cmd, max_read_ops, max_write_ops,
-                         max_read_bytes, max_write_bytes,
-                         have_max_read_ops, have_max_write_ops,
-                         have_max_read_bytes, have_max_write_bytes);
+  bool ratelimit_configured = set_ratelimit_info(ratelimit_info, opt_cmd, max_read_ops, max_write_ops, max_list_ops,
+                         max_delete_ops, max_read_bytes, max_write_bytes,
+                         have_max_read_ops, have_max_write_ops, have_max_list_ops,
+                         have_max_delete_ops, have_max_read_bytes, have_max_write_bytes);
   if (!ratelimit_configured) {
     ldpp_dout(dpp(), 0) << "ERROR: no rate limit values have been specified" << dendl;
     return -EINVAL;
@@ -1618,7 +1632,10 @@ int show_user_ratelimit(std::unique_ptr<rgw::sal::User>& user, Formatter *format
       ldpp_dout(dpp(), 0) << "ERROR: failed to decode rate limit" << dendl;
       return -EIO;
     }
+  } else {
+    return -ENOENT;
   }
+
   formatter->open_object_section("user_ratelimit");
   encode_json("user_ratelimit", ratelimit_info, formatter);
   formatter->close_section();
@@ -3685,12 +3702,16 @@ int main(int argc, const char **argv)
   int64_t max_size = -1;
   int64_t max_read_ops = 0;
   int64_t max_write_ops = 0;
+  int64_t max_list_ops = 0;
+  int64_t max_delete_ops = 0;
   int64_t max_read_bytes = 0;
   int64_t max_write_bytes = 0;
   bool have_max_objects = false;
   bool have_max_size = false;
   bool have_max_write_ops = false;
   bool have_max_read_ops = false;
+  bool have_max_list_ops = false;
+  bool have_max_delete_ops = false;
   bool have_max_write_bytes = false;
   bool have_max_read_bytes = false;
   int include_all = false;
@@ -3980,6 +4001,20 @@ int main(int argc, const char **argv)
         return EINVAL;
       }
       have_max_read_ops = true;
+    } else if (ceph_argparse_witharg(args, i, &val, "--max-list-ops", (char*)NULL)) {
+      max_list_ops = (int64_t)strict_strtoll(val.c_str(), 10, &err);
+      if (!err.empty()) {
+        cerr << "ERROR: failed to parse max list requests: " << err << std::endl;
+        return EINVAL;
+      }
+      have_max_list_ops = true;
+    } else if (ceph_argparse_witharg(args, i, &val, "--max-delete-ops", (char*)NULL)) {
+      max_delete_ops = (int64_t)strict_strtoll(val.c_str(), 10, &err);
+      if (!err.empty()) {
+        cerr << "ERROR: failed to parse max delete requests: " << err << std::endl;
+        return EINVAL;
+      }
+      have_max_delete_ops = true;
     } else if (ceph_argparse_witharg(args, i, &val, "--max-write-ops", (char*)NULL)) {
       max_write_ops = (int64_t)strict_strtoll(val.c_str(), 10, &err);
       if (!err.empty()) {
@@ -4968,24 +5003,24 @@ int main(int argc, const char **argv)
         formatter->open_object_section("period_config");
         if (ratelimit_scope == "bucket") {
           ratelimit_configured = set_ratelimit_info(period_config.bucket_ratelimit, opt_cmd,
-                         max_read_ops, max_write_ops,
+                         max_read_ops, max_write_ops, max_list_ops, max_delete_ops,
                          max_read_bytes, max_write_bytes,
-                         have_max_read_ops, have_max_write_ops,
-                         have_max_read_bytes, have_max_write_bytes);
+                         have_max_read_ops, have_max_write_ops, have_max_list_ops,
+                         have_max_delete_ops, have_max_read_bytes, have_max_write_bytes);
           encode_json("bucket_ratelimit", period_config.bucket_ratelimit, formatter.get());
         } else if (ratelimit_scope == "user") {
           ratelimit_configured = set_ratelimit_info(period_config.user_ratelimit, opt_cmd,
-                         max_read_ops, max_write_ops,
+                         max_read_ops, max_write_ops, max_list_ops, max_delete_ops,
                          max_read_bytes, max_write_bytes,
-                         have_max_read_ops, have_max_write_ops,
-                         have_max_read_bytes, have_max_write_bytes);
+                         have_max_read_ops, have_max_write_ops, have_max_list_ops,
+                         have_max_delete_ops, have_max_read_bytes, have_max_write_bytes);
           encode_json("user_ratelimit", period_config.user_ratelimit, formatter.get());
         } else if (ratelimit_scope == "anonymous") {
           ratelimit_configured = set_ratelimit_info(period_config.anon_ratelimit, opt_cmd,
-                         max_read_ops, max_write_ops,
+                         max_read_ops, max_write_ops, max_list_ops,max_delete_ops,
                          max_read_bytes, max_write_bytes,
-                         have_max_read_ops, have_max_write_ops,
-                         have_max_read_bytes, have_max_write_bytes);
+                         have_max_read_ops, have_max_write_ops, have_max_list_ops,
+                         have_max_delete_ops, have_max_read_bytes, have_max_write_bytes);
           encode_json("anonymous_ratelimit", period_config.anon_ratelimit, formatter.get());
         } else if (ratelimit_scope.empty() && opt_cmd == OPT::GLOBAL_RATELIMIT_GET) {
           // if no scope is given for GET, print both
@@ -6722,7 +6757,8 @@ int main(int argc, const char **argv)
                                         OPT::ROLE_CREATE, OPT::ROLE_DELETE,
                                         OPT::ROLE_POLICY_PUT, OPT::ROLE_POLICY_DELETE,
                                         OPT::ROLE_POLICY_ATTACH, OPT::ROLE_POLICY_DETACH,
-                                        OPT::USER_POLICY_ATTACH, OPT::USER_POLICY_DETACH};
+                                        OPT::USER_POLICY_ATTACH, OPT::USER_POLICY_DETACH,
+                                        OPT::RATELIMIT_SET, OPT::RATELIMIT_ENABLE, OPT::RATELIMIT_DISABLE};
 
   bool print_warning_message = (non_master_ops_list.find(opt_cmd) != non_master_ops_list.end() &&
                                 non_master_cmd);
@@ -11277,15 +11313,15 @@ next:
         return EINVAL;
       }
       return set_bucket_ratelimit(driver, opt_cmd, tenant, bucket_name,
-                           max_read_ops, max_write_ops,
+                           max_read_ops, max_write_ops, max_list_ops, max_delete_ops,
                            max_read_bytes, max_write_bytes,
-                           have_max_read_ops, have_max_write_ops,
+                           have_max_read_ops, have_max_write_ops, have_max_list_ops, have_max_delete_ops,
                            have_max_read_bytes, have_max_write_bytes);
     } else if (!rgw::sal::User::empty(user)) {
       if (ratelimit_scope == "user") {
-        return set_user_ratelimit(opt_cmd, user, max_read_ops, max_write_ops,
+        return set_user_ratelimit(opt_cmd, user, max_read_ops, max_write_ops, max_list_ops, max_delete_ops,
                          max_read_bytes, max_write_bytes,
-                         have_max_read_ops, have_max_write_ops,
+                         have_max_read_ops, have_max_write_ops, have_max_list_ops, have_max_delete_ops,
                          have_max_read_bytes, have_max_write_bytes);
       } else {
         cerr << "ERROR: invalid ratelimit scope specification. Please specify either --ratelimit-scope=bucket, or --ratelimit-scope=user" << std::endl;
@@ -11308,7 +11344,11 @@ next:
       return show_bucket_ratelimit(driver, tenant, bucket_name, formatter.get());
     } else if (!rgw::sal::User::empty(user)) {
       if (ratelimit_scope == "user") {
-        return show_user_ratelimit(user, formatter.get());
+        int ret = show_user_ratelimit(user, formatter.get());
+        if (ret < 0) {
+          std::cerr << "ERROR: failed to get a ratelimit for user id: '" << user->get_id() << "', errno: " << cpp_strerror(-ret) << std::endl;
+        }
+        return ret;
       } else {
         cerr << "ERROR: invalid ratelimit scope specification. Please specify either --ratelimit-scope=bucket, or --ratelimit-scope=user" << std::endl;
         return EINVAL;
index 40895ca56b5b631907e658a8690de9597f5db9b9..1715e816a16c73a267567b8042c817b8ed1e76cc 100644 (file)
@@ -2842,6 +2842,8 @@ void RGWRateLimitInfo::dump(Formatter *f) const
 {
   f->dump_int("max_read_ops", max_read_ops);
   f->dump_int("max_write_ops", max_write_ops);
+  f->dump_int("max_list_ops", max_list_ops);
+  f->dump_int("max_delete_ops", max_delete_ops);
   f->dump_int("max_read_bytes", max_read_bytes);
   f->dump_int("max_write_bytes", max_write_bytes);
   f->dump_bool("enabled", enabled);
index ec62e9c7a8350e827d16b866747ff90b4fb39dc3..4e173541de9afc478a27e7b3fdba0c13ce5b5541 100644 (file)
@@ -568,26 +568,40 @@ class RateLimiter;
 struct RGWRateLimitInfo {
   int64_t max_write_ops;
   int64_t max_read_ops;
+  int64_t max_list_ops;
+  int64_t max_delete_ops;
   int64_t max_write_bytes;
   int64_t max_read_bytes;
   bool enabled = false;
   RGWRateLimitInfo()
-    : max_write_ops(0), max_read_ops(0), max_write_bytes(0), max_read_bytes(0)  {}
+    : max_write_ops(0), max_read_ops(0), max_list_ops(0), max_delete_ops(0), max_write_bytes(0), max_read_bytes(0)  {}
 
   void encode(bufferlist& bl) const {
-    ENCODE_START(1, 1, bl);
+    ENCODE_START(2, 1, bl);
     encode(max_write_ops, bl);
     encode(max_read_ops, bl);
+    encode(max_list_ops, bl);
+    encode(max_delete_ops, bl);
     encode(max_write_bytes, bl);
     encode(max_read_bytes, bl);
     encode(enabled, bl);
     ENCODE_FINISH(bl);
   }
   void decode(bufferlist::const_iterator& bl) {
-    DECODE_START(1, bl);
-    decode(max_write_ops,bl);
+    DECODE_START(2, bl);
+    decode(max_write_ops, bl);
     decode(max_read_ops, bl);
-    decode(max_write_bytes,bl);
+    if (struct_v >= 2) {
+      decode(max_list_ops, bl);
+    } else {
+      max_list_ops = 0;
+    }
+    if (struct_v >= 2) {
+      decode(max_delete_ops, bl);
+    } else {
+      max_delete_ops = 0;
+    }
+    decode(max_write_bytes, bl);
     decode(max_read_bytes, bl);
     decode(enabled, bl);
     DECODE_FINISH(bl);
index d23e9ba3886c0f7925532112e4ed71ef763b4bef..101ed2af6814aaf5d4e494432c0b254da2e9aa8c 100644 (file)
@@ -112,6 +112,14 @@ bool rate_limit(rgw::sal::Driver* driver, req_state* s) {
   s->ratelimit_bucket_marker = bucketfind;
   const char *method = s->info.method;
 
+  bool is_sts_user = (s->auth.identity && s->auth.identity->get_identity_type() == TYPE_ROLE);
+  if (is_sts_user) {
+    ldpp_dout(s, 21) << "STS user detected: uid=" << std::quoted(s->user->get_id().to_str()) << dendl;
+    auto op_ret = s->user->read_attrs(s, s->yield);
+    if (op_ret < 0) {
+      ldpp_dout(s, 0) << "checking rate_limit: uid=" << std::quoted(s->user->get_id().to_str()) << " failed to read user attrs" << dendl;
+    }
+  }
   auto iter = s->user->get_attrs().find(RGW_ATTR_RATELIMIT);
   if(iter != s->user->get_attrs().end()) {
     try {
@@ -126,12 +134,14 @@ bool rate_limit(rgw::sal::Driver* driver, req_state* s) {
       ldpp_dout(s, 0) << "ERROR: failed to decode rate limit" << dendl;
       return -EIO;
     }
+  } else {
+    ldpp_dout(s, 21) << "checking rate_limit: uid=" << std::quoted(s->user->get_id().to_str()) << " does not have a rate limit attribute" << dendl;
   }
   if (s->user->get_id().id == RGW_USER_ANON_ID && global_anon.enabled) {
     *user_ratelimit = global_anon;
   }
   bool limit_bucket = false;
-  bool limit_user = s->ratelimit_data->should_rate_limit(method, s->ratelimit_user_name, s->time, user_ratelimit);
+  bool limit_user = s->ratelimit_data->should_rate_limit(method, s->ratelimit_user_name, s->time, user_ratelimit, s->info.request_params);
 
   if(!rgw::sal::Bucket::empty(s->bucket.get()))
   {
@@ -151,11 +161,11 @@ bool rate_limit(rgw::sal::Driver* driver, req_state* s) {
       }
     }
     if (!limit_user) {
-      limit_bucket = s->ratelimit_data->should_rate_limit(method, s->ratelimit_bucket_marker, s->time, bucket_ratelimit);
+      limit_bucket = s->ratelimit_data->should_rate_limit(method, s->ratelimit_bucket_marker, s->time, bucket_ratelimit, s->info.request_params);
     }
   }
   if(limit_bucket && !limit_user) {
-    s->ratelimit_data->giveback_tokens(method, s->ratelimit_user_name);
+    s->ratelimit_data->giveback_tokens(method, s->ratelimit_user_name, s->info.request_params, user_ratelimit);
   }
   s->user_ratelimit = *user_ratelimit;
   s->bucket_ratelimit = *bucket_ratelimit;
index 224312bb0d78fc74f41a1ab7434fdbb580bc148e..3da2430630731dfc95dd7ff36cdb123c84686fc9 100644 (file)
@@ -5,6 +5,8 @@
 #include <condition_variable>
 #include "rgw_common.h"
 
+enum class OpType { Read, Write, List, Delete };
+
 
 class RateLimiterEntry {
   /* 
@@ -21,6 +23,8 @@ class RateLimiterEntry {
   };
   counters read;
   counters write;
+  counters list;
+  counters del;
   ceph::timespan ts;
   bool first_run = true;
   std::mutex ts_lock;
@@ -33,6 +37,14 @@ class RateLimiterEntry {
   {
     return write.ops / fixed_point_rgw_ratelimit;
   }
+  int64_t list_ops() const
+  {
+    return list.ops / fixed_point_rgw_ratelimit;
+  }
+  int64_t delete_ops() const
+  {
+    return del.ops / fixed_point_rgw_ratelimit;
+  }
   int64_t read_bytes() const
   {
     return read.bytes / fixed_point_rgw_ratelimit;
@@ -52,6 +64,22 @@ class RateLimiterEntry {
     read.ops -= fixed_point_rgw_ratelimit;
     return false;
   }
+  bool should_rate_limit_list(int64_t ops_limit)
+  {
+    if ((list_ops() - 1 < 0) && (ops_limit > 0)) {
+      return true;
+    }
+    list.ops -= fixed_point_rgw_ratelimit;
+    return false;
+  }
+  bool should_rate_limit_delete(int64_t ops_limit)
+  {
+    if ((delete_ops() - 1 < 0) && (ops_limit > 0)) {
+      return true;
+    }
+    del.ops -= fixed_point_rgw_ratelimit;
+    return false;
+  }
   bool should_rate_limit_write(int64_t ops_limit, int64_t bw_limit) 
   {
     //check if tenants did not reach their bw or ops limits and that the limits are not 0 (which is unlimited)
@@ -72,7 +100,8 @@ class RateLimiterEntry {
   bool minimum_time_reached(ceph::timespan curr_timestamp) const
   {
     using namespace std::chrono;
-    constexpr auto min_duration = duration_cast<ceph::timespan>(seconds(60)) / fixed_point_rgw_ratelimit;
+    const size_t accumulation_interval = g_ceph_context->_conf->rgw_ratelimit_interval;
+    const auto min_duration = duration_cast<ceph::timespan>(seconds(accumulation_interval)) / fixed_point_rgw_ratelimit;
     const auto delta = curr_timestamp - ts;
     if (delta < min_duration)
     {
@@ -91,35 +120,48 @@ class RateLimiterEntry {
       write.bytes = info->max_write_bytes * fixed_point;
       read.ops = info->max_read_ops * fixed_point;
       read.bytes = info->max_read_bytes * fixed_point;
+      list.ops = info->max_list_ops * fixed_point;
+      del.ops = info->max_delete_ops * fixed_point;
       ts = curr_timestamp;
       first_run = false;
       return;
     }
     else if(curr_timestamp > ts && minimum_time_reached(curr_timestamp))
     {
-      const int64_t time_in_ms = std::chrono::duration_cast<std::chrono::milliseconds>(curr_timestamp - ts).count() / 60.0 / std::milli::den * fixed_point; // / 60 to make it work with 1 min token bucket
+      const size_t accumulation_interval = g_ceph_context->_conf->rgw_ratelimit_interval;
+      const int64_t time_in_ms = std::chrono::duration_cast<std::chrono::milliseconds>(curr_timestamp - ts).count() / static_cast<double>(accumulation_interval) / std::milli::den * fixed_point;
       ts = curr_timestamp;
       const int64_t write_ops = info->max_write_ops * time_in_ms;
       const int64_t write_bw = info->max_write_bytes * time_in_ms;
       const int64_t read_ops = info->max_read_ops * time_in_ms;
       const int64_t read_bw = info->max_read_bytes * time_in_ms;
+      const int64_t list_ops = info->max_list_ops * time_in_ms;
+      const int64_t delete_ops = info->max_delete_ops * time_in_ms;
       read.ops = std::min(info->max_read_ops * fixed_point, read_ops + read.ops);
       read.bytes = std::min(info->max_read_bytes * fixed_point, read_bw + read.bytes);
       write.ops = std::min(info->max_write_ops * fixed_point, write_ops + write.ops);
       write.bytes = std::min(info->max_write_bytes * fixed_point, write_bw + write.bytes);
+      list.ops = std::min(info->max_list_ops * fixed_point, list_ops + list.ops);
+      del.ops = std::min(info->max_delete_ops * fixed_point, delete_ops + del.ops);
     }
   }
 
   public:
-    bool should_rate_limit(bool is_read, const RGWRateLimitInfo* ratelimit_info, ceph::timespan curr_timestamp)
+    bool should_rate_limit(OpType op_type, const RGWRateLimitInfo* ratelimit_info, ceph::timespan curr_timestamp)
     {
       std::unique_lock lock(ts_lock);
       increase_tokens(curr_timestamp, ratelimit_info);
-      if (is_read)
-      {
-        return should_rate_limit_read(ratelimit_info->max_read_ops, ratelimit_info->max_read_bytes);
+      switch (op_type) {
+        case OpType::Read:
+          return should_rate_limit_read(ratelimit_info->max_read_ops, ratelimit_info->max_read_bytes);
+        case OpType::Write:
+          return should_rate_limit_write(ratelimit_info->max_write_ops, ratelimit_info->max_write_bytes);
+        case OpType::List:
+          return should_rate_limit_list(ratelimit_info->max_list_ops);
+        case OpType::Delete:
+          return should_rate_limit_delete(ratelimit_info->max_delete_ops);
       }
-      return should_rate_limit_write(ratelimit_info->max_write_ops, ratelimit_info->max_write_bytes);
+      return false;
     }
     void decrease_bytes(bool is_read, int64_t amount, const RGWRateLimitInfo* info) {
       std::unique_lock lock(ts_lock);
@@ -131,19 +173,27 @@ class RateLimiterEntry {
         write.bytes = std::max(write.bytes - amount * fixed_point_rgw_ratelimit,info->max_write_bytes * fixed_point_rgw_ratelimit * -2);
       }
     }
-    void giveback_tokens(bool is_read)
+    void giveback_tokens(OpType op_type)
     {
       std::unique_lock lock(ts_lock);
-      if (is_read) 
-      {
-        read.ops += fixed_point_rgw_ratelimit;
-      } else {
-        write.ops += fixed_point_rgw_ratelimit;
+      switch (op_type) {
+        case OpType::Read:
+          read.ops += fixed_point_rgw_ratelimit;
+          break;
+        case OpType::Write:
+          write.ops += fixed_point_rgw_ratelimit;
+          break;
+        case OpType::List:
+          list.ops += fixed_point_rgw_ratelimit;
+          break;
+        case OpType::Delete:
+          del.ops += fixed_point_rgw_ratelimit;
+          break;
       }
     }
 };
 
-class RateLimiter {
+class RateLimiter : public DoutPrefix {
 
   static constexpr size_t map_size = 2000000; // will create it with the closest upper prime number
   std::shared_mutex insert_lock;
@@ -151,12 +201,31 @@ class RateLimiter {
   std::condition_variable& cv;
   typedef std::unordered_map<std::string, RateLimiterEntry> hash_map;
   hash_map ratelimit_entries{map_size};
-  static bool is_read_op(const std::string_view method) {
+
+  static inline constexpr std::string_view RESOURCE_PATTERN_LIST_TYPE = "list-type=";
+  static inline constexpr std::string_view RESOURCE_PATTERN_PREFIX = "prefix=";
+  static inline constexpr std::string_view RESOURCE_PATTERN_DELIMITER = "delimiter=";
+
+
+  static OpType op_type(const std::string_view method, const std::string_view resource, const RGWRateLimitInfo* ratelimit_info) {
+    const bool ratelimit_list = ratelimit_info && (ratelimit_info->max_list_ops > 0);
+    const bool ratelimit_delete = ratelimit_info && (ratelimit_info->max_delete_ops > 0);
+
+    auto contains_any = [](std::string_view s, auto&&... patterns) {
+      return ((s.find(patterns) != std::string::npos) || ...);
+    };
+    if (ratelimit_list && method == "GET" &&
+        !resource.empty() && contains_any(resource, RESOURCE_PATTERN_LIST_TYPE, RESOURCE_PATTERN_PREFIX, RESOURCE_PATTERN_DELIMITER)) {
+      return OpType::List;
+    }
     if (method == "GET" || method == "HEAD")
     {
-      return true;
+      return OpType::Read;
     }
-    return false;
+    if (ratelimit_delete && method == "DELETE") {
+      return OpType::Delete;
+    }
+    return OpType::Write;
   }
 
     // find or create an entry, and return its iterator
@@ -187,41 +256,47 @@ class RateLimiter {
     RateLimiter(RateLimiter&&) = delete;
     RateLimiter& operator =(RateLimiter&&) = delete;
     RateLimiter() = delete;
-    RateLimiter(std::atomic_bool& replacing, std::condition_variable& cv)
-      : replacing(replacing), cv(cv)
+    RateLimiter(CephContext* cct, std::atomic_bool& replacing, std::condition_variable& cv)
+      : DoutPrefix(cct, ceph_subsys_rgw, "rate limiter: "), replacing(replacing), cv(cv)
     {
       // prevents rehash, so no iterators invalidation
       ratelimit_entries.max_load_factor(1000);
     };
 
-    bool should_rate_limit(const char *method, const std::string& key, ceph::coarse_real_time curr_timestamp, const RGWRateLimitInfo* ratelimit_info) {
+    bool should_rate_limit(const char *method, const std::string& key, ceph::coarse_real_time curr_timestamp, const RGWRateLimitInfo* ratelimit_info, const std::string& resource) {
+      ldpp_dout(this, 21) << "checking should_rate_limit: key=" << std::quoted(key) << " enabled=" << ratelimit_info->enabled << dendl;
       if (key.empty() || key.length() == 1 || !ratelimit_info->enabled)
       {
         return false;
       }
-      bool is_read = is_read_op(method);
+      OpType type = op_type(method, resource, ratelimit_info);
       auto& it = find_or_create(key);
       auto curr_ts = curr_timestamp.time_since_epoch();
-      return it.should_rate_limit(is_read ,ratelimit_info, curr_ts);
+      return it.should_rate_limit(type, ratelimit_info, curr_ts);
     }
-    void giveback_tokens(const char *method, const std::string& key)
+    void giveback_tokens(const char *method, const std::string& key, const std::string& resource, const RGWRateLimitInfo* ratelimit_info)
     {
-      bool is_read = is_read_op(method);
+      OpType type = op_type(method, resource, ratelimit_info);
       auto& it = find_or_create(key);
-      it.giveback_tokens(is_read);
+      it.giveback_tokens(type);
     }
     void decrease_bytes(const char *method, const std::string& key, const int64_t amount, const RGWRateLimitInfo* info) {
       if (key.empty() || key.length() == 1 || !info->enabled)
       {
         return;
       }
-      bool is_read = is_read_op(method);
-      if ((is_read && !info->max_read_bytes) || (!is_read && !info->max_write_bytes))
+      OpType type = op_type(method, "", info);
+      // Only read and write ops affect bytes
+      if ((type == OpType::Read && !info->max_read_bytes) || (type == OpType::Write && !info->max_write_bytes))
       {
         return;
       }
       auto& it = find_or_create(key);
-      it.decrease_bytes(is_read, amount, info);
+      if (type == OpType::Read)
+        it.decrease_bytes(true, amount, info);
+      else if (type == OpType::Write)
+        it.decrease_bytes(false, amount, info);
+      // OpType::List does not affect bytes
     }
     void clear() {
       ratelimit_entries.clear();
@@ -271,8 +346,8 @@ class ActiveRateLimiter : public DoutPrefix  {
     ActiveRateLimiter(CephContext* cct) :
       DoutPrefix(cct, ceph_subsys_rgw, "rate limiter: ")
     {
-      ratelimit[0] = std::make_shared<RateLimiter>(replacing, cv);
-      ratelimit[1] = std::make_shared<RateLimiter>(replacing, cv);
+      ratelimit[0] = std::make_shared<RateLimiter>(cct, replacing, cv);
+      ratelimit[1] = std::make_shared<RateLimiter>(cct, replacing, cv);
     }
     ~ActiveRateLimiter() {
       ldpp_dout(this, 20) << "stopping ratelimit_gc thread" << dendl;
index 6637684ad67ebbad2754117da25ae0847fd499af..d27a6037e25e8d7a93794e608b97029afe2f60bb 100644 (file)
@@ -4,6 +4,7 @@
 #include "rgw_sal.h"
 #include "rgw_sal_config.h"
 #include "rgw_process_env.h"
+#include "rgw_op.h"
 
 class RGWOp_Ratelimit_Info : public RGWRESTOp {
 int check_caps(const RGWUserCaps& caps) override {
@@ -135,12 +136,14 @@ class RGWOp_Ratelimit_Set : public RGWRESTOp {
 
   void set_ratelimit_info(bool have_max_read_ops, int64_t max_read_ops, bool have_max_write_ops, int64_t max_write_ops,
                           bool have_max_read_bytes, int64_t max_read_bytes, bool have_max_write_bytes, int64_t max_write_bytes,
+                          bool have_max_list_ops, int64_t max_list_ops, bool have_max_delete_ops, int64_t max_delete_ops,
                           bool have_enabled, bool enabled, bool& ratelimit_configured, RGWRateLimitInfo& ratelimit_info);
 };
 
 
   void RGWOp_Ratelimit_Set::set_ratelimit_info(bool have_max_read_ops, int64_t max_read_ops, bool have_max_write_ops, int64_t max_write_ops,
                           bool have_max_read_bytes, int64_t max_read_bytes, bool have_max_write_bytes, int64_t max_write_bytes,
+                          bool have_max_list_ops, int64_t max_list_ops, bool have_max_delete_ops, int64_t max_delete_ops,
                           bool have_enabled, bool enabled, bool& ratelimit_configured, RGWRateLimitInfo& ratelimit_info) 
   {
     if (have_max_read_ops) {
@@ -167,6 +170,18 @@ class RGWOp_Ratelimit_Set : public RGWRESTOp {
         ratelimit_configured = true;
       }
     }
+    if (have_max_list_ops) {
+      if (max_list_ops >= 0) {
+        ratelimit_info.max_list_ops = max_list_ops;
+        ratelimit_configured = true;
+      }
+    }
+    if (have_max_delete_ops) {
+      if (max_delete_ops >= 0) {
+        ratelimit_info.max_delete_ops = max_delete_ops;
+        ratelimit_configured = true;
+      }
+    }
     if (have_enabled) {
       ratelimit_info.enabled = enabled;
       ratelimit_configured = true;
@@ -199,6 +214,10 @@ void RGWOp_Ratelimit_Set::execute(optional_yield y)
   bool have_max_read_bytes = false;
   int64_t max_write_bytes = 0;
   bool have_max_write_bytes = false;
+  int64_t max_list_ops = 0;
+  bool have_max_list_ops = false;
+  int64_t max_delete_ops = 0;
+  bool have_max_delete_ops = false;
   RESTArgs::get_string(s, "uid", uid_str, &uid_str);
   RESTArgs::get_string(s, "ratelimit-scope", ratelimit_scope, &ratelimit_scope);
   RESTArgs::get_string(s, "bucket", bucket_name, &bucket_name);
@@ -208,6 +227,8 @@ void RGWOp_Ratelimit_Set::execute(optional_yield y)
   op_ret |= RESTArgs::get_int64(s, "max-write-ops", 0, &max_write_ops, &have_max_write_ops);
   op_ret |= RESTArgs::get_int64(s, "max-read-bytes", 0, &max_read_bytes, &have_max_read_bytes);
   op_ret |= RESTArgs::get_int64(s, "max-write-bytes", 0, &max_write_bytes, &have_max_write_bytes);
+  op_ret |= RESTArgs::get_int64(s, "max-list-ops", 0, &max_list_ops, &have_max_list_ops);
+  op_ret |= RESTArgs::get_int64(s, "max-delete-ops", 0, &max_delete_ops, &have_max_delete_ops);
   if (op_ret) {
     ldpp_dout(this, 0) << "one of the maximum arguments could not be parsed" << dendl;
     return;
@@ -232,8 +253,18 @@ void RGWOp_Ratelimit_Set::execute(optional_yield y)
     }
   }
   RESTArgs::get_bool(s, "global", false, &global, nullptr);
+
+  // forward to master zonegroup
+  op_ret = rgw_forward_request_to_master(this, *s->penv.site, s->user->get_id(),
+                                         nullptr, nullptr, s->info, s->err, y);
+  if (op_ret < 0) {
+    ldpp_dout(this, 0) << "ERROR: forward_request_to_master returned ret=" << op_ret << dendl;
+    return;
+  }
+
   set_ratelimit_info(have_max_read_ops, max_read_ops, have_max_write_ops, max_write_ops,
                      have_max_read_bytes, max_read_bytes, have_max_write_bytes, max_write_bytes,
+                     have_max_list_ops, max_list_ops, have_max_delete_ops, max_delete_ops,
                      have_enabled, enabled, ratelimit_configured, ratelimit_info);
   if (op_ret) {
     return;
@@ -267,6 +298,7 @@ void RGWOp_Ratelimit_Set::execute(optional_yield y)
     }
     set_ratelimit_info(have_max_read_ops, max_read_ops, have_max_write_ops, max_write_ops,
                        have_max_read_bytes, max_read_bytes, have_max_write_bytes, max_write_bytes,
+                       have_max_list_ops, max_list_ops, have_max_delete_ops, max_delete_ops,
                        have_enabled, enabled, ratelimit_configured, ratelimit_info);
     bufferlist bl;
     ratelimit_info.encode(bl);
@@ -300,6 +332,7 @@ void RGWOp_Ratelimit_Set::execute(optional_yield y)
     bufferlist bl;
     set_ratelimit_info(have_max_read_ops, max_read_ops, have_max_write_ops, max_write_ops,
                        have_max_read_bytes, max_read_bytes, have_max_write_bytes, max_write_bytes,
+                       have_max_list_ops, max_list_ops, have_max_delete_ops, max_delete_ops,
                        have_enabled, enabled, ratelimit_configured, ratelimit_info);
     ratelimit_info.encode(bl);
     rgw::sal::Attrs attr;
@@ -321,6 +354,7 @@ void RGWOp_Ratelimit_Set::execute(optional_yield y)
       ratelimit_info = period_config.bucket_ratelimit;
       set_ratelimit_info(have_max_read_ops, max_read_ops, have_max_write_ops, max_write_ops,
                          have_max_read_bytes, max_read_bytes, have_max_write_bytes, max_write_bytes,
+                         have_max_list_ops, max_list_ops, have_max_delete_ops, max_delete_ops,
                          have_enabled, enabled, ratelimit_configured, ratelimit_info);
       period_config.bucket_ratelimit = ratelimit_info;
       op_ret = cfgstore->write_period_config(s, y, false, realm_id, period_config);
@@ -330,6 +364,7 @@ void RGWOp_Ratelimit_Set::execute(optional_yield y)
       ratelimit_info = period_config.anon_ratelimit;
       set_ratelimit_info(have_max_read_ops, max_read_ops, have_max_write_ops, max_write_ops,
                          have_max_read_bytes, max_read_bytes, have_max_write_bytes, max_write_bytes,
+                         have_max_list_ops, max_list_ops, have_max_delete_ops, max_delete_ops,
                          have_enabled, enabled, ratelimit_configured, ratelimit_info);
       period_config.anon_ratelimit = ratelimit_info;
       op_ret = cfgstore->write_period_config(s, y, false, realm_id, period_config);
@@ -339,6 +374,7 @@ void RGWOp_Ratelimit_Set::execute(optional_yield y)
       ratelimit_info = period_config.user_ratelimit;
       set_ratelimit_info(have_max_read_ops, max_read_ops, have_max_write_ops, max_write_ops,
                          have_max_read_bytes, max_read_bytes, have_max_write_bytes, max_write_bytes,
+                         have_max_list_ops, max_list_ops, have_max_delete_ops, max_delete_ops,
                          have_enabled, enabled, ratelimit_configured, ratelimit_info);
       period_config.user_ratelimit = ratelimit_info;
       op_ret = cfgstore->write_period_config(s, y, false, realm_id, period_config);
index 2146de6ed8a93d1eb2b40b650d0e14c4265dc964..d1bd6ea637de8493b88588b327fc27f913b35c11 100644 (file)
      --quota-scope                 scope of quota (bucket, user, account)
   
   Rate limiting options:
-     --max-read-ops                specify max requests per minute for READ ops per RGW (GET and HEAD request methods), 0 means unlimited
-     --max-read-bytes              specify max bytes per minute for READ ops per RGW (GET and HEAD request methods), 0 means unlimited
-     --max-write-ops               specify max requests per minute for WRITE ops per RGW (Not GET or HEAD request methods), 0 means unlimited
-     --max-write-bytes             specify max bytes per minute for WRITE ops per RGW (Not GET or HEAD request methods), 0 means unlimited
+     --max-read-ops                specify max requests per accumulation interval for READ ops per RGW (GET and HEAD request methods), 0 means unlimited
+     --max-read-bytes              specify max bytes per accumulation interval for READ ops per RGW (GET and HEAD request methods), 0 means unlimited
+     --max-write-ops               specify max requests per accumulation interval for WRITE ops per RGW (Not GET or HEAD request methods), 0 means unlimited
+     --max-write-bytes             specify max bytes per accumulation interval for WRITE ops per RGW (Not GET or HEAD request methods), 0 means unlimited
+     --max-list-ops                specify max requests per accumulation interval for bucket listing requests per RGW, 0 means unlimited
+     --max-delete-ops              specify max requests per accumulation interval for DELETE ops per RGW (DELETE request methods), 0 means unlimited
      --ratelimit-scope             scope of rate limiting: bucket, user, anonymous
                                    anonymous can be configured only with global rate limit
   
index 529d8f739fd9307ec967c8dc0a168a5b16183dbb..21ed60f07b5585e1403667eeddf1964352912ccd 100644 (file)
@@ -91,7 +91,7 @@ bool simulate_request(client_info& it, const RGWRateLimitInfo& info, std::shared
     int rw = 0; // will always use PUT method as there is no different
     std::string methodop = method[rw];
     auto dout = DoutPrefix(g_ceph_context, ceph_subsys_rgw, "rate limiter: ");
-    bool to_fail = ratelimit->should_rate_limit(methodop.c_str(), it.tenant, time, &info);
+    bool to_fail = ratelimit->should_rate_limit(methodop.c_str(), it.tenant, time, &info, "");
     if(to_fail)
     {
         it.rejected++;
index ae422e1dada275b3609dfc9e981b808c58149407..3b837b98f74eb35d9fc3efba8d5b0c67c93b8fb6 100644 (file)
@@ -46,7 +46,7 @@ int main(int argc, char **argv)
     {
         std::string tenant = "uuser" + std::to_string(i);
         auto time = ceph::coarse_real_clock::now();
-        ratelimit->get_active()->should_rate_limit("PUT", tenant, time, &info);
+        ratelimit->get_active()->should_rate_limit("PUT", tenant, time, &info, "");
     }
 
 }
index 01be4df48a025833a4debef9629df9357898412d..fd305fdfad0a8ff93b9ecb4a90e44b0a541d8ef6 100644 (file)
@@ -7,16 +7,17 @@
 
 using namespace std::chrono_literals;
 
+
 TEST(RGWRateLimit, op_limit_not_enabled)
 {
   // info.enabled = false, so no limit
   std::atomic_bool replacing;
   std::condition_variable cv;
-  RateLimiter ratelimit(replacing, cv);
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
   RGWRateLimitInfo info;
   auto time = ceph::coarse_real_clock::now();
   std::string key = "uuser123";
-  bool success = ratelimit.should_rate_limit("PUT", key, time, &info);
+  bool success = ratelimit.should_rate_limit("PUT", key, time, &info, "");
   EXPECT_EQ(false, success);
 }
 TEST(RGWRateLimit, reject_op_over_limit)
@@ -24,15 +25,15 @@ TEST(RGWRateLimit, reject_op_over_limit)
   // check that request is being rejected because there are not enough tokens
   std::atomic_bool replacing;
   std::condition_variable cv;
-  RateLimiter ratelimit(replacing, cv);
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
   RGWRateLimitInfo info;
   info.enabled = true;
   info.max_read_ops = 1;
   auto time = ceph::coarse_real_clock::now();
   std::string key = "uuser123";
-  bool success = ratelimit.should_rate_limit("GET", key, time, &info);
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
   time = ceph::coarse_real_clock::now();
-  success = ratelimit.should_rate_limit("GET", key, time, &info);
+  success = ratelimit.should_rate_limit("GET", key, time, &info, "");
   EXPECT_EQ(true, success);
 }
 TEST(RGWRateLimit, accept_op_after_giveback)
@@ -40,16 +41,16 @@ TEST(RGWRateLimit, accept_op_after_giveback)
   // check that giveback is working fine
   std::atomic_bool replacing;
   std::condition_variable cv;
-  RateLimiter ratelimit(replacing, cv);
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
   RGWRateLimitInfo info;
   info.enabled = true;
   info.max_read_ops = 1;
   auto time = ceph::coarse_real_clock::now();
   std::string key = "uuser123";
-  bool success = ratelimit.should_rate_limit("GET", key, time, &info);
-  ratelimit.giveback_tokens("GET", key);
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
+  ratelimit.giveback_tokens("GET", key, "", &info);
   time = ceph::coarse_real_clock::now();
-  success = ratelimit.should_rate_limit("GET", key, time, &info);
+  success = ratelimit.should_rate_limit("GET", key, time, &info, "");
   EXPECT_EQ(false, success);
 }
 TEST(RGWRateLimit, accept_op_after_refill)
@@ -57,15 +58,15 @@ TEST(RGWRateLimit, accept_op_after_refill)
   // check that tokens are being filled properly
   std::atomic_bool replacing;
   std::condition_variable cv;
-  RateLimiter ratelimit(replacing, cv);
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
   RGWRateLimitInfo info;
   info.enabled = true;
   info.max_read_ops = 1;
   auto time = ceph::coarse_real_clock::now();
   std::string key = "uuser123";
-  bool success = ratelimit.should_rate_limit("GET", key, time, &info);
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
   time += 61s;
-  success = ratelimit.should_rate_limit("GET", key, time, &info);
+  success = ratelimit.should_rate_limit("GET", key, time, &info, "");
   EXPECT_EQ(false, success);
 }
 TEST(RGWRateLimit, reject_bw_over_limit)
@@ -73,16 +74,16 @@ TEST(RGWRateLimit, reject_bw_over_limit)
   // check that a newer request is rejected if there is no enough tokens (bw)
   std::atomic_bool replacing;
   std::condition_variable cv;
-  RateLimiter ratelimit(replacing, cv);
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
   RGWRateLimitInfo info;
   info.enabled = true;
   info.max_read_bytes = 1;
   auto time = ceph::coarse_real_clock::now();
   std::string key = "uuser123";
-  bool success = ratelimit.should_rate_limit("GET", key, time, &info);
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
   ratelimit.decrease_bytes("GET",key, 2, &info);
   time = ceph::coarse_real_clock::now();
-  success = ratelimit.should_rate_limit("GET", key, time, &info);
+  success = ratelimit.should_rate_limit("GET", key, time, &info, "");
   EXPECT_EQ(true, success);
 }
 TEST(RGWRateLimit, accept_bw)
@@ -90,16 +91,16 @@ TEST(RGWRateLimit, accept_bw)
   // check that when there are enough tokens (bw) the request is still being served
   std::atomic_bool replacing;
   std::condition_variable cv;
-  RateLimiter ratelimit(replacing, cv);
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
   RGWRateLimitInfo info;
   info.enabled = true;
   info.max_read_bytes = 2;
   auto time = ceph::coarse_real_clock::now();
   std::string key = "uuser123";
-  bool success = ratelimit.should_rate_limit("GET", key, time, &info);
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
   ratelimit.decrease_bytes("GET",key, 1, &info);
   time = ceph::coarse_real_clock::now();
-  success = ratelimit.should_rate_limit("GET", key, time, &info);
+  success = ratelimit.should_rate_limit("GET", key, time, &info, "");
   EXPECT_EQ(false, success);
 }
 TEST(RGWRateLimit, check_bw_debt_at_max_120secs)
@@ -107,16 +108,16 @@ TEST(RGWRateLimit, check_bw_debt_at_max_120secs)
   // check that the bandwidth debt is not larger than 120 seconds
   std::atomic_bool replacing;
   std::condition_variable cv;
-  RateLimiter ratelimit(replacing, cv);
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
   RGWRateLimitInfo info;
   info.enabled = true;
   info.max_read_bytes = 2;
   auto time = ceph::coarse_real_clock::now();
   std::string key = "uuser123";
-  bool success = ratelimit.should_rate_limit("GET", key, time, &info);
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
   ratelimit.decrease_bytes("GET",key, 100, &info);
   time += 121s;
-  success = ratelimit.should_rate_limit("GET", key, time, &info);
+  success = ratelimit.should_rate_limit("GET", key, time, &info, "");
   EXPECT_EQ(false, success);
 }
 TEST(RGWRateLimit, check_that_bw_limit_not_affect_ops)
@@ -124,17 +125,17 @@ TEST(RGWRateLimit, check_that_bw_limit_not_affect_ops)
   // check that high read bytes limit, does not affect ops limit
   std::atomic_bool replacing;
   std::condition_variable cv;
-  RateLimiter ratelimit(replacing, cv);
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
   RGWRateLimitInfo info;
   info.enabled = true;
   info.max_read_ops = 1;
   info.max_read_bytes = 100000000;
   auto time = ceph::coarse_real_clock::now();
   std::string key = "uuser123";
-  bool success = ratelimit.should_rate_limit("GET", key, time, &info);
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
   ratelimit.decrease_bytes("GET",key, 10000, &info);
   time = ceph::coarse_real_clock::now();
-  success = ratelimit.should_rate_limit("GET", key, time, &info);
+  success = ratelimit.should_rate_limit("GET", key, time, &info, "");
   EXPECT_EQ(true, success);
 }
 TEST(RGWRateLimit, read_limit_does_not_affect_writes)
@@ -142,17 +143,17 @@ TEST(RGWRateLimit, read_limit_does_not_affect_writes)
   // read limit does not affect writes
   std::atomic_bool replacing;
   std::condition_variable cv;
-  RateLimiter ratelimit(replacing, cv);
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
   RGWRateLimitInfo info;
   info.enabled = true;
   info.max_read_ops = 1;
   info.max_read_bytes = 100000000;
   auto time = ceph::coarse_real_clock::now();
   std::string key = "uuser123";
-  bool success = ratelimit.should_rate_limit("PUT", key, time, &info);
+  bool success = ratelimit.should_rate_limit("PUT", key, time, &info, "");
   ratelimit.decrease_bytes("PUT",key, 10000, &info);
   time = ceph::coarse_real_clock::now();
-  success = ratelimit.should_rate_limit("PUT", key, time, &info);
+  success = ratelimit.should_rate_limit("PUT", key, time, &info, "");
   EXPECT_EQ(false, success);
 }
 TEST(RGWRateLimit, write_limit_does_not_affect_reads)
@@ -160,17 +161,17 @@ TEST(RGWRateLimit, write_limit_does_not_affect_reads)
   // write limit does not affect reads
   std::atomic_bool replacing;
   std::condition_variable cv;
-  RateLimiter ratelimit(replacing, cv);
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
   RGWRateLimitInfo info;
   info.enabled = true;
   info.max_write_ops = 1;
   info.max_write_bytes = 100000000;
   auto time = ceph::coarse_real_clock::now();
   std::string key = "uuser123";
-  bool success = ratelimit.should_rate_limit("GET", key, time, &info);
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
   ratelimit.decrease_bytes("GET",key, 10000, &info);
   time = ceph::coarse_real_clock::now();
-  success = ratelimit.should_rate_limit("GET", key, time, &info);
+  success = ratelimit.should_rate_limit("GET", key, time, &info, "");
   EXPECT_EQ(false, success);
 }
 
@@ -179,12 +180,12 @@ TEST(RGWRateLimit, allow_unlimited_access)
   // 0 values in RGWRateLimitInfo should allow unlimited access
   std::atomic_bool replacing;
   std::condition_variable cv;
-  RateLimiter ratelimit(replacing, cv);
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
   RGWRateLimitInfo info;
   info.enabled = true;
   auto time = ceph::coarse_real_clock::now();
   std::string key = "uuser123";
-  bool success = ratelimit.should_rate_limit("GET", key, time, &info);
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
   EXPECT_EQ(false, success);
 }
 
@@ -199,7 +200,7 @@ TEST(RGWRateLimitGC, NO_GC_AHEAD_OF_TIME)
   RGWRateLimitInfo info;
   auto time = ceph::coarse_real_clock::now();
   std::string key = "uuser123";
-  active->should_rate_limit("GET", key, time, &info);
+  active->should_rate_limit("GET", key, time, &info, "");
   auto activegc = ratelimit->get_active();
   EXPECT_EQ(activegc, active);
 }
@@ -217,7 +218,7 @@ TEST(RGWRateLimiterGC, GC_IS_WORKING)
   std::string key = "-1";
   for(int i = 0; i < 2000000; i++)
   {
-    active->should_rate_limit("GET", key, time, &info);
+    active->should_rate_limit("GET", key, time, &info, "");
     key = std::to_string(i);
   }
   auto activegc = ratelimit->get_active();
@@ -231,7 +232,7 @@ TEST(RGWRateLimitEntry, op_limit_not_enabled)
   RateLimiterEntry entry;
   RGWRateLimitInfo info;
   auto time = ceph::coarse_real_clock::now().time_since_epoch();
-  bool success = entry.should_rate_limit(false, &info, time);
+  bool success = entry.should_rate_limit(OpType::Read, &info, time);
   EXPECT_EQ(false, success);
 }
 TEST(RGWRateLimitEntry, reject_op_over_limit)
@@ -243,9 +244,9 @@ TEST(RGWRateLimitEntry, reject_op_over_limit)
   info.enabled = true;
   info.max_read_ops = 1;
   auto time = ceph::coarse_real_clock::now().time_since_epoch();
-  bool success = entry.should_rate_limit(true, &info, time);
+  bool success = entry.should_rate_limit(OpType::Read, &info, time);
   time = ceph::coarse_real_clock::now().time_since_epoch();
-  success = entry.should_rate_limit(true, &info, time);
+  success = entry.should_rate_limit(OpType::Read, &info, time);
   EXPECT_EQ(true, success);
 }
 TEST(RGWRateLimitEntry, accept_op_after_giveback)
@@ -256,10 +257,10 @@ TEST(RGWRateLimitEntry, accept_op_after_giveback)
   info.enabled = true;
   info.max_read_ops = 1;
   auto time = ceph::coarse_real_clock::now().time_since_epoch();
-  bool success = entry.should_rate_limit(true,  &info, time);
-  entry.giveback_tokens(true);
+  bool success = entry.should_rate_limit(OpType::Read,  &info, time);
+  entry.giveback_tokens(OpType::Read);
   time = ceph::coarse_real_clock::now().time_since_epoch();
-  success = entry.should_rate_limit(true,  &info, time);
+  success = entry.should_rate_limit(OpType::Read,  &info, time);
   EXPECT_EQ(false, success);
 }
 TEST(RGWRateLimitEntry, accept_op_after_refill)
@@ -270,9 +271,9 @@ TEST(RGWRateLimitEntry, accept_op_after_refill)
   info.enabled = true;
   info.max_read_ops = 1;
   auto time = ceph::coarse_real_clock::now().time_since_epoch();
-  bool success = entry.should_rate_limit(true,  &info, time);
+  bool success = entry.should_rate_limit(OpType::Read,  &info, time);
   time += 61s;
-  success = entry.should_rate_limit(true,  &info, time);
+  success = entry.should_rate_limit(OpType::Read,  &info, time);
   EXPECT_EQ(false, success);
 }
 TEST(RGWRateLimitEntry, reject_bw_over_limit)
@@ -283,10 +284,10 @@ TEST(RGWRateLimitEntry, reject_bw_over_limit)
   info.enabled = true;
   info.max_read_bytes = 1;
   auto time = ceph::coarse_real_clock::now().time_since_epoch();
-  bool success = entry.should_rate_limit(true,  &info, time);
+  bool success = entry.should_rate_limit(OpType::Read,  &info, time);
   entry.decrease_bytes(true, 2, &info);
   time = ceph::coarse_real_clock::now().time_since_epoch();
-  success = entry.should_rate_limit(true,  &info, time);
+  success = entry.should_rate_limit(OpType::Read,  &info, time);
   EXPECT_EQ(true, success);
 }
 TEST(RGWRateLimitEntry, accept_bw)
@@ -297,10 +298,10 @@ TEST(RGWRateLimitEntry, accept_bw)
   info.enabled = true;
   info.max_read_bytes = 2;
   auto time = ceph::coarse_real_clock::now().time_since_epoch();
-  bool success = entry.should_rate_limit(true,  &info, time);
+  bool success = entry.should_rate_limit(OpType::Read, &info, time);
   entry.decrease_bytes(true, 1, &info);
   time = ceph::coarse_real_clock::now().time_since_epoch();
-  success = entry.should_rate_limit(true,  &info, time);
+  success = entry.should_rate_limit(OpType::Read, &info, time);
   EXPECT_EQ(false, success);
 }
 TEST(RGWRateLimitEntry, check_bw_debt_at_max_120secs)
@@ -311,10 +312,10 @@ TEST(RGWRateLimitEntry, check_bw_debt_at_max_120secs)
   info.enabled = true;
   info.max_read_bytes = 2;
   auto time = ceph::coarse_real_clock::now().time_since_epoch();
-  bool success = entry.should_rate_limit(true,  &info, time);
+  bool success = entry.should_rate_limit(OpType::Read, &info, time);
   entry.decrease_bytes(true, 100, &info);
   time += 121s;
-  success = entry.should_rate_limit(true,  &info, time);
+  success = entry.should_rate_limit(OpType::Read, &info, time);
   EXPECT_EQ(false, success);
 }
 TEST(RGWRateLimitEntry, check_that_bw_limit_not_affect_ops)
@@ -326,10 +327,10 @@ TEST(RGWRateLimitEntry, check_that_bw_limit_not_affect_ops)
   info.max_read_ops = 1;
   info.max_read_bytes = 100000000;
   auto time = ceph::coarse_real_clock::now().time_since_epoch();
-  bool success = entry.should_rate_limit(true,  &info, time);
+  bool success = entry.should_rate_limit(OpType::Read, &info, time);
   entry.decrease_bytes(true, 10000, &info);
   time = ceph::coarse_real_clock::now().time_since_epoch();
-  success = entry.should_rate_limit(true,  &info, time);
+  success = entry.should_rate_limit(OpType::Read, &info, time);
   EXPECT_EQ(true, success);
 }
 TEST(RGWRateLimitEntry, read_limit_does_not_affect_writes)
@@ -341,10 +342,10 @@ TEST(RGWRateLimitEntry, read_limit_does_not_affect_writes)
   info.max_read_ops = 1;
   info.max_read_bytes = 100000000;
   auto time = ceph::coarse_real_clock::now().time_since_epoch();
-  bool success = entry.should_rate_limit(false,  &info, time);
+  bool success = entry.should_rate_limit(OpType::Write, &info, time);
   entry.decrease_bytes(false, 10000, &info);
   time = ceph::coarse_real_clock::now().time_since_epoch();
-  success = entry.should_rate_limit(false,  &info, time);
+  success = entry.should_rate_limit(OpType::Write, &info, time);
   EXPECT_EQ(false, success);
 }
 TEST(RGWRateLimitEntry, write_limit_does_not_affect_reads)
@@ -357,10 +358,10 @@ TEST(RGWRateLimitEntry, write_limit_does_not_affect_reads)
   info.max_write_bytes = 100000000;
   auto time = ceph::coarse_real_clock::now().time_since_epoch();
   std::string key = "uuser123";
-  bool success = entry.should_rate_limit(true,  &info, time);
+  bool success = entry.should_rate_limit(OpType::Read, &info, time);
   entry.decrease_bytes(true, 10000, &info);
   time = ceph::coarse_real_clock::now().time_since_epoch();
-  success = entry.should_rate_limit(true,  &info, time);
+  success = entry.should_rate_limit(OpType::Read, &info, time);
   EXPECT_EQ(false, success);
 }
 
@@ -371,6 +372,642 @@ TEST(RGWRateLimitEntry, allow_unlimited_access)
   RGWRateLimitInfo info;
   info.enabled = true;
   auto time = ceph::coarse_real_clock::now().time_since_epoch();
-  bool success = entry.should_rate_limit(true,  &info, time);
+  bool success = entry.should_rate_limit(OpType::Read, &info, time);
+  EXPECT_EQ(false, success);
+}
+
+
+// Helpers for LIST op resource string
+// ref uri: "/aaa-gonzo-staging-bbb-checkpoint1-us-west-0000?list-type=2&delimiter=%2F&max-keys=2&prefix=spark%2Fgonzo-avro%2Fsplunk_hec_test%2Fchunk-commits%2F%2F00000007999&encoding-type=url";
+const std::string RES_LIST_TYPE_2 = "?list-type=2";
+const std::string RES_DELIMITER = "&delimiter=%2F";
+const std::string RES_PREFIX = "&prefix=spark%2Fgonzo-avro%2Fsplunk_hec_test%2Fchunk-commits%2F%2F00000007999";
+
+TEST(RGWRateLimit, reject_list_op_over_limit)
+{
+  // check that LIST op is being rejected because there are not enough tokens
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_list";
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+  time = ceph::coarse_real_clock::now();
+  success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+  EXPECT_EQ(true, success);
+}
+
+TEST(RGWRateLimit, accept_list_op_after_giveback)
+{
+  // check that giveback is working for LIST ops
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_list";
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+  ratelimit.giveback_tokens("GET", key, RES_LIST_TYPE_2, &info);
+  time = ceph::coarse_real_clock::now();
+  success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, accept_list_op_after_refill)
+{
+  // check that tokens are being filled properly for LIST ops
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_list";
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+  time += 61s;
+  success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, list_limit_does_not_affect_reads)
+{
+  // list limit does not affect reads
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  info.max_read_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_list";
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+  // Should still be able to do a normal GET (read)
+  success = ratelimit.should_rate_limit("GET", key, time, &info, "");
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, read_limit_does_not_affect_lists)
+{
+  // read limit does not affect lists
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  info.max_read_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_list";
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
+  // Should still be able to do a LIST op
+  success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, list_limit_does_not_affect_writes)
+{
+  // list limit does not affect writes
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  info.max_write_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_list";
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+  // Should still be able to do a PUT (write)
+  success = ratelimit.should_rate_limit("PUT", key, time, &info, "");
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, write_limit_does_not_affect_lists)
+{
+  // write limit does not affect lists
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  info.max_write_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_list";
+  bool success = ratelimit.should_rate_limit("PUT", key, time, &info, "");
+  // Should still be able to do a LIST op
+  success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, list_limit_does_not_affect_deletes)
+{
+  // list limit does not affect deletes
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  info.max_delete_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_list";
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+  // Should still be able to do a DELETE op
+  success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, delete_limit_does_not_affect_lists)
+{
+  // delete limit does not affect lists
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  info.max_delete_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_list";
+  bool success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+  // Should still be able to do a LIST op
+  success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+  EXPECT_EQ(false, success);
+}
+
+// LIST RES_DELIMITER minimal tests
+TEST(RGWRateLimit, reject_delimiter_op_over_limit)
+{
+  // check that DELIMITER op is being rejected because there are not enough tokens
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_list";
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_DELIMITER);
+  time = ceph::coarse_real_clock::now();
+  success = ratelimit.should_rate_limit("GET", key, time, &info, RES_DELIMITER);
+  EXPECT_EQ(true, success);
+}
+
+TEST(RGWRateLimit, accept_delimiter_op_after_giveback)
+{
+  // check that giveback is working for DELIMITER ops
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_list";
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_DELIMITER);
+  ratelimit.giveback_tokens("GET", key, RES_DELIMITER, &info);
+  time = ceph::coarse_real_clock::now();
+  success = ratelimit.should_rate_limit("GET", key, time, &info, RES_DELIMITER);
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, accept_delimiter_op_after_refill)
+{
+  // check that tokens are being filled properly for DELIMITER ops
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_list";
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_DELIMITER);
+  time += 61s;
+  success = ratelimit.should_rate_limit("GET", key, time, &info, RES_DELIMITER);
+  EXPECT_EQ(false, success);
+}
+
+// LIST RES_PREFIX minimal tests
+TEST(RGWRateLimit, reject_prefix_op_over_limit)
+{
+  // check that PREFIX op is being rejected because there are not enough tokens
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_list";
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_PREFIX);
+  time = ceph::coarse_real_clock::now();
+  success = ratelimit.should_rate_limit("GET", key, time, &info, RES_PREFIX);
+  EXPECT_EQ(true, success);
+}
+
+TEST(RGWRateLimit, accept_prefix_op_after_giveback)
+{
+  // check that giveback is working for PREFIX ops
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_list";
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_PREFIX);
+  ratelimit.giveback_tokens("GET", key, RES_PREFIX, &info);
+  time = ceph::coarse_real_clock::now();
+  success = ratelimit.should_rate_limit("GET", key, time, &info, RES_PREFIX);
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, accept_prefix_op_after_refill)
+{
+  // check that tokens are being filled properly for PREFIX ops
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_list";
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_PREFIX);
+  time += 61s;
+  success = ratelimit.should_rate_limit("GET", key, time, &info, RES_PREFIX);
+  EXPECT_EQ(false, success);
+}
+
+
+TEST(RGWRateLimitEntry, reject_list_op_over_limit)
+{
+  // check that LIST request is being rejected because there are not enough tokens
+  RGWRateLimitInfo info;
+  RateLimiterEntry entry;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  auto time = ceph::coarse_real_clock::now().time_since_epoch();
+  bool success = entry.should_rate_limit(OpType::List, &info, time);
+  time = ceph::coarse_real_clock::now().time_since_epoch();
+  success = entry.should_rate_limit(OpType::List, &info, time);
+  EXPECT_EQ(true, success);
+}
+
+TEST(RGWRateLimitEntry, accept_list_op_after_giveback)
+{
+  // check that giveback is working fine for LIST ops
+  RGWRateLimitInfo info;
+  RateLimiterEntry entry;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  auto time = ceph::coarse_real_clock::now().time_since_epoch();
+  bool success = entry.should_rate_limit(OpType::List, &info, time);
+  entry.giveback_tokens(OpType::List);
+  time = ceph::coarse_real_clock::now().time_since_epoch();
+  success = entry.should_rate_limit(OpType::List, &info, time);
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, accept_list_op_after_refill)
+{
+  // check that tokens are being filled properly for LIST ops
+  RateLimiterEntry entry;
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  auto time = ceph::coarse_real_clock::now().time_since_epoch();
+  bool success = entry.should_rate_limit(OpType::List, &info, time);
+  time += 61s;
+  success = entry.should_rate_limit(OpType::List, &info, time);
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, list_limit_does_not_affect_reads)
+{
+  // list limit does not affect reads
+  RateLimiterEntry entry;
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  info.max_read_ops = 1;
+  auto time = ceph::coarse_real_clock::now().time_since_epoch();
+  bool success = entry.should_rate_limit(OpType::List, &info, time);
+  time = ceph::coarse_real_clock::now().time_since_epoch();
+  success = entry.should_rate_limit(OpType::Read, &info, time);
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, read_limit_does_not_affect_lists)
+{
+  // read limit does not affect lists
+  RateLimiterEntry entry;
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  info.max_read_ops = 1;
+  auto time = ceph::coarse_real_clock::now().time_since_epoch();
+  bool success = entry.should_rate_limit(OpType::Read, &info, time);
+  time = ceph::coarse_real_clock::now().time_since_epoch();
+  success = entry.should_rate_limit(OpType::List, &info, time);
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, list_limit_does_not_affect_writes)
+{
+  // list limit does not affect writes
+  RateLimiterEntry entry;
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  info.max_write_ops = 1;
+  auto time = ceph::coarse_real_clock::now().time_since_epoch();
+  bool success = entry.should_rate_limit(OpType::List, &info, time);
+  time = ceph::coarse_real_clock::now().time_since_epoch();
+  success = entry.should_rate_limit(OpType::Write, &info, time);
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, write_limit_does_not_affect_lists)
+{
+  // write limit does not affect lists
+  RateLimiterEntry entry;
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  info.max_write_ops = 1;
+  auto time = ceph::coarse_real_clock::now().time_since_epoch();
+  bool success = entry.should_rate_limit(OpType::Write, &info, time);
+  time = ceph::coarse_real_clock::now().time_since_epoch();
+  success = entry.should_rate_limit(OpType::List, &info, time);
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, list_limit_does_not_affect_deletes)
+{
+  // list limit does not affect deletes
+  RateLimiterEntry entry;
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  info.max_delete_ops = 1;
+  auto time = ceph::coarse_real_clock::now().time_since_epoch();
+  bool success = entry.should_rate_limit(OpType::List, &info, time);
+  time = ceph::coarse_real_clock::now().time_since_epoch();
+  success = entry.should_rate_limit(OpType::Delete, &info, time);
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, delete_limit_does_not_affect_lists)
+{
+  // delete limit does not affect lists
+  RateLimiterEntry entry;
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_list_ops = 1;
+  info.max_delete_ops = 1;
+  auto time = ceph::coarse_real_clock::now().time_since_epoch();
+  bool success = entry.should_rate_limit(OpType::Delete, &info, time);
+  time = ceph::coarse_real_clock::now().time_since_epoch();
+  success = entry.should_rate_limit(OpType::List, &info, time);
+  EXPECT_EQ(false, success);
+}
+
+
+TEST(RGWRateLimit, reject_delete_op_over_limit)
+{
+  // check that DELETE op is being rejected because there are not enough tokens
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_delete_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_delete";
+  bool success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+  time = ceph::coarse_real_clock::now();
+  success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+  EXPECT_EQ(true, success);
+}
+
+TEST(RGWRateLimit, accept_delete_op_after_giveback)
+{
+  // check that giveback is working for DELETE ops
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_delete_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_delete";
+  bool success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+  ratelimit.giveback_tokens("DELETE", key, "", &info);
+  time = ceph::coarse_real_clock::now();
+  success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, accept_delete_op_after_refill)
+{
+  // check that tokens are being filled properly for DELETE ops
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_delete_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_delete";
+  bool success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+  time += 61s;
+  success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, delete_limit_does_not_affect_reads)
+{
+  // delete limit does not affect reads
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_delete_ops = 1;
+  info.max_read_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_delete";
+  bool success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+  // Should still be able to do a normal GET (read)
+  success = ratelimit.should_rate_limit("GET", key, time, &info, "");
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, read_limit_does_not_affect_deletes)
+{
+  // read limit does not affect deletes
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_delete_ops = 1;
+  info.max_read_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_delete";
+  bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
+  // Should still be able to do a DELETE op
+  success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, write_limit_does_not_affect_deletes)
+{
+  // write limit does not affect deletes
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_delete_ops = 1;
+  info.max_write_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_delete";
+  bool success = ratelimit.should_rate_limit("PUT", key, time, &info, "");
+  // Should still be able to do a DELETE op
+  success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, delete_limit_does_not_affect_writes)
+{
+  // delete limit does not affect writes
+  std::atomic_bool replacing;
+  std::condition_variable cv;
+  RateLimiter ratelimit(g_ceph_context, replacing, cv);
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_delete_ops = 1;
+  info.max_write_ops = 1;
+  auto time = ceph::coarse_real_clock::now();
+  std::string key = "uuser_delete";
+  bool success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+  // Should still be able to do a PUT (write)
+  success = ratelimit.should_rate_limit("PUT", key, time, &info, "");
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, reject_delete_op_over_limit)
+{
+  // check that DELETE request is being rejected because there are not enough tokens
+  RGWRateLimitInfo info;
+  RateLimiterEntry entry;
+  info.enabled = true;
+  info.max_delete_ops = 1;
+  auto time = ceph::coarse_real_clock::now().time_since_epoch();
+  bool success = entry.should_rate_limit(OpType::Delete, &info, time);
+  time = ceph::coarse_real_clock::now().time_since_epoch();
+  success = entry.should_rate_limit(OpType::Delete, &info, time);
+  EXPECT_EQ(true, success);
+}
+
+TEST(RGWRateLimitEntry, accept_delete_op_after_giveback)
+{
+  // check that giveback is working fine for DELETE ops
+  RGWRateLimitInfo info;
+  RateLimiterEntry entry;
+  info.enabled = true;
+  info.max_delete_ops = 1;
+  auto time = ceph::coarse_real_clock::now().time_since_epoch();
+  bool success = entry.should_rate_limit(OpType::Delete, &info, time);
+  entry.giveback_tokens(OpType::Delete);
+  time = ceph::coarse_real_clock::now().time_since_epoch();
+  success = entry.should_rate_limit(OpType::Delete, &info, time);
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, accept_delete_op_after_refill)
+{
+  // check that tokens are being filled properly for DELETE ops
+  RateLimiterEntry entry;
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_delete_ops = 1;
+  auto time = ceph::coarse_real_clock::now().time_since_epoch();
+  bool success = entry.should_rate_limit(OpType::Delete, &info, time);
+  time += 61s;
+  success = entry.should_rate_limit(OpType::Delete, &info, time);
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, delete_limit_does_not_affect_reads)
+{
+  // delete limit does not affect reads
+  RateLimiterEntry entry;
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_delete_ops = 1;
+  info.max_read_ops = 1;
+  auto time = ceph::coarse_real_clock::now().time_since_epoch();
+  bool success = entry.should_rate_limit(OpType::Delete, &info, time);
+  time = ceph::coarse_real_clock::now().time_since_epoch();
+  success = entry.should_rate_limit(OpType::Read, &info, time);
   EXPECT_EQ(false, success);
 }
+
+TEST(RGWRateLimitEntry, read_limit_does_not_affect_deletes)
+{
+  // read limit does not affect deletes
+  RateLimiterEntry entry;
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_delete_ops = 1;
+  info.max_read_ops = 1;
+  auto time = ceph::coarse_real_clock::now().time_since_epoch();
+  bool success = entry.should_rate_limit(OpType::Read, &info, time);
+  time = ceph::coarse_real_clock::now().time_since_epoch();
+  success = entry.should_rate_limit(OpType::Delete, &info, time);
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, write_limit_does_not_affect_deletes)
+{
+  // write limit does not affect deletes
+  RateLimiterEntry entry;
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_delete_ops = 1;
+  info.max_write_ops = 1;
+  auto time = ceph::coarse_real_clock::now().time_since_epoch();
+  bool success = entry.should_rate_limit(OpType::Write, &info, time);
+  time = ceph::coarse_real_clock::now().time_since_epoch();
+  success = entry.should_rate_limit(OpType::Delete, &info, time);
+  EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, delete_limit_does_not_affect_writes)
+{
+  // delete limit does not affect writes
+  RateLimiterEntry entry;
+  RGWRateLimitInfo info;
+  info.enabled = true;
+  info.max_delete_ops = 1;
+  info.max_write_ops = 1;
+  auto time = ceph::coarse_real_clock::now().time_since_epoch();
+  bool success = entry.should_rate_limit(OpType::Delete, &info, time);
+  time = ceph::coarse_real_clock::now().time_since_epoch();
+  success = entry.should_rate_limit(OpType::Write, &info, time);
+  EXPECT_EQ(false, success);
+}
+