]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
rgw: add rate limit for LIST & DELETE ops
authorMark Kogan <mkogan@ibm.com>
Wed, 30 Jul 2025 12:54:19 +0000 (12:54 +0000)
committerThomas Serlin <tserlin@redhat.com>
Mon, 22 Sep 2025 19:18:18 +0000 (15:18 -0400)
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))
                                      ^^^
```

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

Resolves: rhbz#2391529

Co-authored-by: Yuval Lifshitz <yuvalif@yahoo.com>
Signed-off-by: Mark Kogan <31659604+mkogan1@users.noreply.github.com>
(cherry picked from commit 965eda7a45b12c9ccd78f230076002043f7df65c)
Signed-off-by: Matt Benjamin <mbenjamin@redhat.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 68ba361bca926c799706b193301f5170841ea6af..0be5cf2669ab10246b9aef389f2d8e7aa668696e 100644 (file)
@@ -585,6 +585,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 4dd9329fc658403d05387836fc240e2cf2a6c404..a2307344eb772bc706b4fddbe02a3a50ae4186dc 100644 (file)
@@ -623,9 +623,13 @@ 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)
 
 Read Requests and Write Requests
 --------------------------------
@@ -637,8 +641,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
@@ -655,10 +659,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.
 
 
@@ -669,19 +673,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
@@ -700,7 +712,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: 
@@ -765,7 +778,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 2a0010d1ddcb5dd0cb60c7921fa4044c0a865f7f..de52476b22a64fa8dff4c7ebce9db476b64ee3c4 100644 (file)
@@ -2038,8 +2038,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.
@@ -2053,16 +2054,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'.
@@ -2089,7 +2096,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>]>
 
 
 
@@ -2109,7 +2116,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>]>
 
 
 
@@ -2129,7 +2136,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>]>
 
 
 
@@ -2139,7 +2146,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>]>
 
 
 
@@ -2149,7 +2156,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 577f2200d139853b293800791027cbcfdcb8a878..31996f9fb801f211fad2ef6f31cadb071ec53580 100644 (file)
@@ -4430,3 +4430,29 @@ 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
+  services:
+  - rgw
+  flags:
+  - startup
+  with_legacy: true
+- name: rgw_redis_connection_pool_size
+  type: int
+  level: basic
+  desc: RGW connection pool size for Redis operation per D4N
+  long_desc: This option sets the size of the connection pool for Redis operations
+    in D4N. It is used to manage the number of concurrent connections to Redis.
+    A larger pool size can improve performance when multiple threads are accessing
+    Redis simultaneously, but it also increases resource usage.
+  fmt_desc: The size of the redis connection pool.
+  default: 512
+  services:
+  - rgw
+  with_legacy: true
index 15064d4f860e01f13c3ca20d12570da5afaac1c6..fb475ba95132a40268d9e3cf7f2b4b227a76afbb 100644 (file)
@@ -494,10 +494,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";
@@ -1412,9 +1414,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) {
@@ -1438,6 +1440,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;
@@ -1522,10 +1536,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),
@@ -1546,10 +1560,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;
@@ -1567,10 +1581,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);
@@ -1585,10 +1599,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;
@@ -1619,7 +1633,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();
@@ -3686,12 +3703,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;
@@ -3981,6 +4002,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
@@ -11277,15 +11312,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 +11343,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 6cd1571100de8662677f438a290baa329e82e065..728eb36db001c5371f8846607a7e939bdb217536 100644 (file)
@@ -2836,6 +2836,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 95fbf66ba28d66515b253917f29c1fdca73c8911..41e69eeb4db5ec77dde79b56b63010ced6c7d762 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..e70b68c0794873876224ef41fcfbc06ea7cc282c 100644 (file)
@@ -131,7 +131,7 @@ bool rate_limit(rgw::sal::Driver* driver, req_state* s) {
     *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 +151,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..e1b877378ff630bfe9296f565069c4f5b3fff890 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,14 +173,22 @@ 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;
       }
     }
 };
@@ -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
@@ -194,34 +263,39 @@ class RateLimiter {
       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) {
       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();
index 128e44d9a5ae0032c34719a9d7c6add31d7e7ae7..60ae5dd3d205848b227e147890eb1e74d04a138e 100644 (file)
@@ -130,12 +130,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) {
@@ -162,6 +164,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;
@@ -194,6 +208,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);
@@ -203,6 +221,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;
@@ -229,6 +249,7 @@ void RGWOp_Ratelimit_Set::execute(optional_yield y)
   RESTArgs::get_bool(s, "global", false, &global, nullptr);
   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;
@@ -262,6 +283,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);
@@ -295,6 +317,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;
@@ -314,6 +337,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 = period_config.write(s, static_cast<rgw::sal::RadosStore*>(driver)->svc()->sysobj, realm_id, y);
@@ -323,6 +347,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 = period_config.write(s, static_cast<rgw::sal::RadosStore*>(driver)->svc()->sysobj, realm_id, y);
@@ -332,6 +357,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 = period_config.write(s, static_cast<rgw::sal::RadosStore*>(driver)->svc()->sysobj, realm_id, y);
index 6e6b170755e21fd051ca959283e2548857dc6ea2..031d4c0922c9eccc278f37966918bfa877770f08 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..c2ffa92b5ce9183ad65c00f1e995d83c88cf3747 100644 (file)
@@ -7,6 +7,7 @@
 
 using namespace std::chrono_literals;
 
+
 TEST(RGWRateLimit, op_limit_not_enabled)
 {
   // info.enabled = false, so no limit
@@ -16,7 +17,7 @@ TEST(RGWRateLimit, op_limit_not_enabled)
   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)
@@ -30,9 +31,9 @@ TEST(RGWRateLimit, reject_op_over_limit)
   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)
@@ -46,10 +47,10 @@ TEST(RGWRateLimit, accept_op_after_giveback)
   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)
@@ -63,9 +64,9 @@ TEST(RGWRateLimit, accept_op_after_refill)
   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)
@@ -79,10 +80,10 @@ TEST(RGWRateLimit, reject_bw_over_limit)
   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)
@@ -96,10 +97,10 @@ TEST(RGWRateLimit, accept_bw)
   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)
@@ -113,10 +114,10 @@ TEST(RGWRateLimit, check_bw_debt_at_max_120secs)
   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)
@@ -131,10 +132,10 @@ TEST(RGWRateLimit, check_that_bw_limit_not_affect_ops)
   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)
@@ -149,10 +150,10 @@ TEST(RGWRateLimit, read_limit_does_not_affect_writes)
   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)
@@ -167,10 +168,10 @@ TEST(RGWRateLimit, write_limit_does_not_affect_reads)
   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);
 }
 
@@ -184,7 +185,7 @@ TEST(RGWRateLimit, allow_unlimited_access)
   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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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);
+}
+