* 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.
=====================
Quotas can be set for The Ceph Object Gateway on users and buckets. The "rate
-limit" includes the maximum number of read operations (read ops) and write
-operations (write ops) per minute as well as the number of bytes per minute
-that can be written or read per user or per bucket.
+limit" includes the maximum number of read operations and write operations
+per accumulation interval as well as the number of bytes per accumulation interval
+that can be written or read per user or per bucket. It also includes the maximum
+number of list requests and delete operations per accumulation interval.
+The accumulation interval is configured by the :confval:`rgw_ratelimit_interval` option.
+The default value is 60 seconds.
+(Note: S3 Multi-Object Delete operation are currently not supported by rate limiting)
+
+The configured limits should be divided by the number of active object gateways. For example,
+if "user A" is to be be limited to 10 ops per minute and there are two object gateways in the cluster,
+then the limit on "user A" should be 5 (10 ops per minute / 2 RGWs).
+If the requests are not balanced between RGWs, the rate limit might be underutilized.
+For example: if the ops limit is 5 and there are two RGWs,
+but the Load Balancer sends load to only one of those RGWs,
+the effective limit is 5 ops, because this limit is enforced per RGW.
Read Requests and Write Requests
--------------------------------
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
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.
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
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:
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:
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.
- **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'.
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>]>
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>]>
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>]>
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>]>
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>]>
# 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
+
default: false
services:
- rgw
+- name: rgw_ratelimit_interval
+ type: uint
+ level: advanced
+ desc: Time window for rate limiting in seconds
+ long_desc: This option sets the time window for rate limiting accumulation in seconds.
+ Requests that exceed the configured rate limits within this time window will be rejected.
+ The default is a 60 second token bucket.
+ default: 60
+ min: 1
+ services:
+ - rgw
+ flags:
+ - startup
+ with_legacy: true
- name: rgw_redis_connection_pool_size
type: int
level: basic
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";
}
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) {
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;
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),
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;
}
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);
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;
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();
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;
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()) {
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
OPT::ROLE_CREATE, OPT::ROLE_DELETE,
OPT::ROLE_POLICY_PUT, OPT::ROLE_POLICY_DELETE,
OPT::ROLE_POLICY_ATTACH, OPT::ROLE_POLICY_DETACH,
- OPT::USER_POLICY_ATTACH, OPT::USER_POLICY_DETACH};
+ OPT::USER_POLICY_ATTACH, OPT::USER_POLICY_DETACH,
+ OPT::RATELIMIT_SET, OPT::RATELIMIT_ENABLE, OPT::RATELIMIT_DISABLE};
bool print_warning_message = (non_master_ops_list.find(opt_cmd) != non_master_ops_list.end() &&
non_master_cmd);
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;
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;
{
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);
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);
s->ratelimit_bucket_marker = bucketfind;
const char *method = s->info.method;
+ bool is_sts_user = (s->auth.identity && s->auth.identity->get_identity_type() == TYPE_ROLE);
+ if (is_sts_user) {
+ ldpp_dout(s, 21) << "STS user detected: uid=" << std::quoted(s->user->get_id().to_str()) << dendl;
+ auto op_ret = s->user->read_attrs(s, s->yield);
+ if (op_ret < 0) {
+ ldpp_dout(s, 0) << "checking rate_limit: uid=" << std::quoted(s->user->get_id().to_str()) << " failed to read user attrs" << dendl;
+ }
+ }
auto iter = s->user->get_attrs().find(RGW_ATTR_RATELIMIT);
if(iter != s->user->get_attrs().end()) {
try {
ldpp_dout(s, 0) << "ERROR: failed to decode rate limit" << dendl;
return -EIO;
}
+ } else {
+ ldpp_dout(s, 21) << "checking rate_limit: uid=" << std::quoted(s->user->get_id().to_str()) << " does not have a rate limit attribute" << dendl;
}
if (s->user->get_id().id == RGW_USER_ANON_ID && global_anon.enabled) {
*user_ratelimit = global_anon;
}
bool limit_bucket = false;
- bool limit_user = s->ratelimit_data->should_rate_limit(method, s->ratelimit_user_name, s->time, user_ratelimit);
+ bool limit_user = s->ratelimit_data->should_rate_limit(method, s->ratelimit_user_name, s->time, user_ratelimit, s->info.request_params);
if(!rgw::sal::Bucket::empty(s->bucket.get()))
{
}
}
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;
#include <condition_variable>
#include "rgw_common.h"
+enum class OpType { Read, Write, List, Delete };
+
class RateLimiterEntry {
/*
};
counters read;
counters write;
+ counters list;
+ counters del;
ceph::timespan ts;
bool first_run = true;
std::mutex ts_lock;
{
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;
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)
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)
{
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);
write.bytes = std::max(write.bytes - amount * fixed_point_rgw_ratelimit,info->max_write_bytes * fixed_point_rgw_ratelimit * -2);
}
}
- void giveback_tokens(bool is_read)
+ void giveback_tokens(OpType op_type)
{
std::unique_lock lock(ts_lock);
- if (is_read)
- {
- read.ops += fixed_point_rgw_ratelimit;
- } else {
- write.ops += fixed_point_rgw_ratelimit;
+ switch (op_type) {
+ case OpType::Read:
+ read.ops += fixed_point_rgw_ratelimit;
+ break;
+ case OpType::Write:
+ write.ops += fixed_point_rgw_ratelimit;
+ break;
+ case OpType::List:
+ list.ops += fixed_point_rgw_ratelimit;
+ break;
+ case OpType::Delete:
+ del.ops += fixed_point_rgw_ratelimit;
+ break;
}
}
};
-class RateLimiter {
+class RateLimiter : public DoutPrefix {
static constexpr size_t map_size = 2000000; // will create it with the closest upper prime number
std::shared_mutex insert_lock;
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
RateLimiter(RateLimiter&&) = delete;
RateLimiter& operator =(RateLimiter&&) = delete;
RateLimiter() = delete;
- RateLimiter(std::atomic_bool& replacing, std::condition_variable& cv)
- : replacing(replacing), cv(cv)
+ RateLimiter(CephContext* cct, std::atomic_bool& replacing, std::condition_variable& cv)
+ : DoutPrefix(cct, ceph_subsys_rgw, "rate limiter: "), replacing(replacing), cv(cv)
{
// prevents rehash, so no iterators invalidation
ratelimit_entries.max_load_factor(1000);
};
- bool should_rate_limit(const char *method, const std::string& key, ceph::coarse_real_time curr_timestamp, const RGWRateLimitInfo* ratelimit_info) {
+ bool should_rate_limit(const char *method, const std::string& key, ceph::coarse_real_time curr_timestamp, const RGWRateLimitInfo* ratelimit_info, const std::string& resource) {
+ ldpp_dout(this, 21) << "checking should_rate_limit: key=" << std::quoted(key) << " enabled=" << ratelimit_info->enabled << dendl;
if (key.empty() || key.length() == 1 || !ratelimit_info->enabled)
{
return false;
}
- bool is_read = is_read_op(method);
+ OpType type = op_type(method, resource, ratelimit_info);
auto& it = find_or_create(key);
auto curr_ts = curr_timestamp.time_since_epoch();
- return it.should_rate_limit(is_read ,ratelimit_info, curr_ts);
+ return it.should_rate_limit(type, ratelimit_info, curr_ts);
}
- void giveback_tokens(const char *method, const std::string& key)
+ void giveback_tokens(const char *method, const std::string& key, const std::string& resource, const RGWRateLimitInfo* ratelimit_info)
{
- bool is_read = is_read_op(method);
+ OpType type = op_type(method, resource, ratelimit_info);
auto& it = find_or_create(key);
- it.giveback_tokens(is_read);
+ it.giveback_tokens(type);
}
void decrease_bytes(const char *method, const std::string& key, const int64_t amount, const RGWRateLimitInfo* info) {
if (key.empty() || key.length() == 1 || !info->enabled)
{
return;
}
- bool is_read = is_read_op(method);
- if ((is_read && !info->max_read_bytes) || (!is_read && !info->max_write_bytes))
+ OpType type = op_type(method, "", info);
+ // Only read and write ops affect bytes
+ if ((type == OpType::Read && !info->max_read_bytes) || (type == OpType::Write && !info->max_write_bytes))
{
return;
}
auto& it = find_or_create(key);
- it.decrease_bytes(is_read, amount, info);
+ if (type == OpType::Read)
+ it.decrease_bytes(true, amount, info);
+ else if (type == OpType::Write)
+ it.decrease_bytes(false, amount, info);
+ // OpType::List does not affect bytes
}
void clear() {
ratelimit_entries.clear();
ActiveRateLimiter(CephContext* cct) :
DoutPrefix(cct, ceph_subsys_rgw, "rate limiter: ")
{
- ratelimit[0] = std::make_shared<RateLimiter>(replacing, cv);
- ratelimit[1] = std::make_shared<RateLimiter>(replacing, cv);
+ ratelimit[0] = std::make_shared<RateLimiter>(cct, replacing, cv);
+ ratelimit[1] = std::make_shared<RateLimiter>(cct, replacing, cv);
}
~ActiveRateLimiter() {
ldpp_dout(this, 20) << "stopping ratelimit_gc thread" << dendl;
#include "rgw_sal.h"
#include "rgw_sal_config.h"
#include "rgw_process_env.h"
+#include "rgw_op.h"
class RGWOp_Ratelimit_Info : public RGWRESTOp {
int check_caps(const RGWUserCaps& caps) override {
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) {
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;
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);
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;
}
}
RESTArgs::get_bool(s, "global", false, &global, nullptr);
+
+ // forward to master zonegroup
+ op_ret = rgw_forward_request_to_master(this, *s->penv.site, s->user->get_id(),
+ nullptr, nullptr, s->info, s->err, y);
+ if (op_ret < 0) {
+ ldpp_dout(this, 0) << "ERROR: forward_request_to_master returned ret=" << op_ret << dendl;
+ return;
+ }
+
set_ratelimit_info(have_max_read_ops, max_read_ops, have_max_write_ops, max_write_ops,
have_max_read_bytes, max_read_bytes, have_max_write_bytes, max_write_bytes,
+ have_max_list_ops, max_list_ops, have_max_delete_ops, max_delete_ops,
have_enabled, enabled, ratelimit_configured, ratelimit_info);
if (op_ret) {
return;
}
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);
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;
ratelimit_info = period_config.bucket_ratelimit;
set_ratelimit_info(have_max_read_ops, max_read_ops, have_max_write_ops, max_write_ops,
have_max_read_bytes, max_read_bytes, have_max_write_bytes, max_write_bytes,
+ have_max_list_ops, max_list_ops, have_max_delete_ops, max_delete_ops,
have_enabled, enabled, ratelimit_configured, ratelimit_info);
period_config.bucket_ratelimit = ratelimit_info;
op_ret = cfgstore->write_period_config(s, y, false, realm_id, period_config);
ratelimit_info = period_config.anon_ratelimit;
set_ratelimit_info(have_max_read_ops, max_read_ops, have_max_write_ops, max_write_ops,
have_max_read_bytes, max_read_bytes, have_max_write_bytes, max_write_bytes,
+ have_max_list_ops, max_list_ops, have_max_delete_ops, max_delete_ops,
have_enabled, enabled, ratelimit_configured, ratelimit_info);
period_config.anon_ratelimit = ratelimit_info;
op_ret = cfgstore->write_period_config(s, y, false, realm_id, period_config);
ratelimit_info = period_config.user_ratelimit;
set_ratelimit_info(have_max_read_ops, max_read_ops, have_max_write_ops, max_write_ops,
have_max_read_bytes, max_read_bytes, have_max_write_bytes, max_write_bytes,
+ have_max_list_ops, max_list_ops, have_max_delete_ops, max_delete_ops,
have_enabled, enabled, ratelimit_configured, ratelimit_info);
period_config.user_ratelimit = ratelimit_info;
op_ret = cfgstore->write_period_config(s, y, false, realm_id, period_config);
--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
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++;
{
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, "");
}
}
using namespace std::chrono_literals;
+
TEST(RGWRateLimit, op_limit_not_enabled)
{
// info.enabled = false, so no limit
std::atomic_bool replacing;
std::condition_variable cv;
- RateLimiter ratelimit(replacing, cv);
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
RGWRateLimitInfo info;
auto time = ceph::coarse_real_clock::now();
std::string key = "uuser123";
- bool success = ratelimit.should_rate_limit("PUT", key, time, &info);
+ bool success = ratelimit.should_rate_limit("PUT", key, time, &info, "");
EXPECT_EQ(false, success);
}
TEST(RGWRateLimit, reject_op_over_limit)
// check that request is being rejected because there are not enough tokens
std::atomic_bool replacing;
std::condition_variable cv;
- RateLimiter ratelimit(replacing, cv);
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
RGWRateLimitInfo info;
info.enabled = true;
info.max_read_ops = 1;
auto time = ceph::coarse_real_clock::now();
std::string key = "uuser123";
- bool success = ratelimit.should_rate_limit("GET", key, time, &info);
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
time = ceph::coarse_real_clock::now();
- success = ratelimit.should_rate_limit("GET", key, time, &info);
+ success = ratelimit.should_rate_limit("GET", key, time, &info, "");
EXPECT_EQ(true, success);
}
TEST(RGWRateLimit, accept_op_after_giveback)
// check that giveback is working fine
std::atomic_bool replacing;
std::condition_variable cv;
- RateLimiter ratelimit(replacing, cv);
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
RGWRateLimitInfo info;
info.enabled = true;
info.max_read_ops = 1;
auto time = ceph::coarse_real_clock::now();
std::string key = "uuser123";
- bool success = ratelimit.should_rate_limit("GET", key, time, &info);
- ratelimit.giveback_tokens("GET", key);
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
+ ratelimit.giveback_tokens("GET", key, "", &info);
time = ceph::coarse_real_clock::now();
- success = ratelimit.should_rate_limit("GET", key, time, &info);
+ success = ratelimit.should_rate_limit("GET", key, time, &info, "");
EXPECT_EQ(false, success);
}
TEST(RGWRateLimit, accept_op_after_refill)
// check that tokens are being filled properly
std::atomic_bool replacing;
std::condition_variable cv;
- RateLimiter ratelimit(replacing, cv);
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
RGWRateLimitInfo info;
info.enabled = true;
info.max_read_ops = 1;
auto time = ceph::coarse_real_clock::now();
std::string key = "uuser123";
- bool success = ratelimit.should_rate_limit("GET", key, time, &info);
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
time += 61s;
- success = ratelimit.should_rate_limit("GET", key, time, &info);
+ success = ratelimit.should_rate_limit("GET", key, time, &info, "");
EXPECT_EQ(false, success);
}
TEST(RGWRateLimit, reject_bw_over_limit)
// check that a newer request is rejected if there is no enough tokens (bw)
std::atomic_bool replacing;
std::condition_variable cv;
- RateLimiter ratelimit(replacing, cv);
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
RGWRateLimitInfo info;
info.enabled = true;
info.max_read_bytes = 1;
auto time = ceph::coarse_real_clock::now();
std::string key = "uuser123";
- bool success = ratelimit.should_rate_limit("GET", key, time, &info);
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
ratelimit.decrease_bytes("GET",key, 2, &info);
time = ceph::coarse_real_clock::now();
- success = ratelimit.should_rate_limit("GET", key, time, &info);
+ success = ratelimit.should_rate_limit("GET", key, time, &info, "");
EXPECT_EQ(true, success);
}
TEST(RGWRateLimit, accept_bw)
// check that when there are enough tokens (bw) the request is still being served
std::atomic_bool replacing;
std::condition_variable cv;
- RateLimiter ratelimit(replacing, cv);
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
RGWRateLimitInfo info;
info.enabled = true;
info.max_read_bytes = 2;
auto time = ceph::coarse_real_clock::now();
std::string key = "uuser123";
- bool success = ratelimit.should_rate_limit("GET", key, time, &info);
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
ratelimit.decrease_bytes("GET",key, 1, &info);
time = ceph::coarse_real_clock::now();
- success = ratelimit.should_rate_limit("GET", key, time, &info);
+ success = ratelimit.should_rate_limit("GET", key, time, &info, "");
EXPECT_EQ(false, success);
}
TEST(RGWRateLimit, check_bw_debt_at_max_120secs)
// check that the bandwidth debt is not larger than 120 seconds
std::atomic_bool replacing;
std::condition_variable cv;
- RateLimiter ratelimit(replacing, cv);
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
RGWRateLimitInfo info;
info.enabled = true;
info.max_read_bytes = 2;
auto time = ceph::coarse_real_clock::now();
std::string key = "uuser123";
- bool success = ratelimit.should_rate_limit("GET", key, time, &info);
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
ratelimit.decrease_bytes("GET",key, 100, &info);
time += 121s;
- success = ratelimit.should_rate_limit("GET", key, time, &info);
+ success = ratelimit.should_rate_limit("GET", key, time, &info, "");
EXPECT_EQ(false, success);
}
TEST(RGWRateLimit, check_that_bw_limit_not_affect_ops)
// check that high read bytes limit, does not affect ops limit
std::atomic_bool replacing;
std::condition_variable cv;
- RateLimiter ratelimit(replacing, cv);
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
RGWRateLimitInfo info;
info.enabled = true;
info.max_read_ops = 1;
info.max_read_bytes = 100000000;
auto time = ceph::coarse_real_clock::now();
std::string key = "uuser123";
- bool success = ratelimit.should_rate_limit("GET", key, time, &info);
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
ratelimit.decrease_bytes("GET",key, 10000, &info);
time = ceph::coarse_real_clock::now();
- success = ratelimit.should_rate_limit("GET", key, time, &info);
+ success = ratelimit.should_rate_limit("GET", key, time, &info, "");
EXPECT_EQ(true, success);
}
TEST(RGWRateLimit, read_limit_does_not_affect_writes)
// read limit does not affect writes
std::atomic_bool replacing;
std::condition_variable cv;
- RateLimiter ratelimit(replacing, cv);
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
RGWRateLimitInfo info;
info.enabled = true;
info.max_read_ops = 1;
info.max_read_bytes = 100000000;
auto time = ceph::coarse_real_clock::now();
std::string key = "uuser123";
- bool success = ratelimit.should_rate_limit("PUT", key, time, &info);
+ bool success = ratelimit.should_rate_limit("PUT", key, time, &info, "");
ratelimit.decrease_bytes("PUT",key, 10000, &info);
time = ceph::coarse_real_clock::now();
- success = ratelimit.should_rate_limit("PUT", key, time, &info);
+ success = ratelimit.should_rate_limit("PUT", key, time, &info, "");
EXPECT_EQ(false, success);
}
TEST(RGWRateLimit, write_limit_does_not_affect_reads)
// write limit does not affect reads
std::atomic_bool replacing;
std::condition_variable cv;
- RateLimiter ratelimit(replacing, cv);
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
RGWRateLimitInfo info;
info.enabled = true;
info.max_write_ops = 1;
info.max_write_bytes = 100000000;
auto time = ceph::coarse_real_clock::now();
std::string key = "uuser123";
- bool success = ratelimit.should_rate_limit("GET", key, time, &info);
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
ratelimit.decrease_bytes("GET",key, 10000, &info);
time = ceph::coarse_real_clock::now();
- success = ratelimit.should_rate_limit("GET", key, time, &info);
+ success = ratelimit.should_rate_limit("GET", key, time, &info, "");
EXPECT_EQ(false, success);
}
// 0 values in RGWRateLimitInfo should allow unlimited access
std::atomic_bool replacing;
std::condition_variable cv;
- RateLimiter ratelimit(replacing, cv);
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
RGWRateLimitInfo info;
info.enabled = true;
auto time = ceph::coarse_real_clock::now();
std::string key = "uuser123";
- bool success = ratelimit.should_rate_limit("GET", key, time, &info);
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
EXPECT_EQ(false, success);
}
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);
}
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();
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)
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)
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)
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)
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)
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)
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)
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)
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)
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);
}
RGWRateLimitInfo info;
info.enabled = true;
auto time = ceph::coarse_real_clock::now().time_since_epoch();
- bool success = entry.should_rate_limit(true, &info, time);
+ bool success = entry.should_rate_limit(OpType::Read, &info, time);
+ EXPECT_EQ(false, success);
+}
+
+
+// Helpers for LIST op resource string
+// ref uri: "/aaa-gonzo-staging-bbb-checkpoint1-us-west-0000?list-type=2&delimiter=%2F&max-keys=2&prefix=spark%2Fgonzo-avro%2Fsplunk_hec_test%2Fchunk-commits%2F%2F00000007999&encoding-type=url";
+const std::string RES_LIST_TYPE_2 = "?list-type=2";
+const std::string RES_DELIMITER = "&delimiter=%2F";
+const std::string RES_PREFIX = "&prefix=spark%2Fgonzo-avro%2Fsplunk_hec_test%2Fchunk-commits%2F%2F00000007999";
+
+TEST(RGWRateLimit, reject_list_op_over_limit)
+{
+ // check that LIST op is being rejected because there are not enough tokens
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_list";
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+ time = ceph::coarse_real_clock::now();
+ success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+ EXPECT_EQ(true, success);
+}
+
+TEST(RGWRateLimit, accept_list_op_after_giveback)
+{
+ // check that giveback is working for LIST ops
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_list";
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+ ratelimit.giveback_tokens("GET", key, RES_LIST_TYPE_2, &info);
+ time = ceph::coarse_real_clock::now();
+ success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, accept_list_op_after_refill)
+{
+ // check that tokens are being filled properly for LIST ops
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_list";
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+ time += 61s;
+ success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, list_limit_does_not_affect_reads)
+{
+ // list limit does not affect reads
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ info.max_read_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_list";
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+ // Should still be able to do a normal GET (read)
+ success = ratelimit.should_rate_limit("GET", key, time, &info, "");
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, read_limit_does_not_affect_lists)
+{
+ // read limit does not affect lists
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ info.max_read_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_list";
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
+ // Should still be able to do a LIST op
+ success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, list_limit_does_not_affect_writes)
+{
+ // list limit does not affect writes
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ info.max_write_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_list";
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+ // Should still be able to do a PUT (write)
+ success = ratelimit.should_rate_limit("PUT", key, time, &info, "");
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, write_limit_does_not_affect_lists)
+{
+ // write limit does not affect lists
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ info.max_write_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_list";
+ bool success = ratelimit.should_rate_limit("PUT", key, time, &info, "");
+ // Should still be able to do a LIST op
+ success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, list_limit_does_not_affect_deletes)
+{
+ // list limit does not affect deletes
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ info.max_delete_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_list";
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+ // Should still be able to do a DELETE op
+ success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, delete_limit_does_not_affect_lists)
+{
+ // delete limit does not affect lists
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ info.max_delete_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_list";
+ bool success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+ // Should still be able to do a LIST op
+ success = ratelimit.should_rate_limit("GET", key, time, &info, RES_LIST_TYPE_2);
+ EXPECT_EQ(false, success);
+}
+
+// LIST RES_DELIMITER minimal tests
+TEST(RGWRateLimit, reject_delimiter_op_over_limit)
+{
+ // check that DELIMITER op is being rejected because there are not enough tokens
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_list";
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_DELIMITER);
+ time = ceph::coarse_real_clock::now();
+ success = ratelimit.should_rate_limit("GET", key, time, &info, RES_DELIMITER);
+ EXPECT_EQ(true, success);
+}
+
+TEST(RGWRateLimit, accept_delimiter_op_after_giveback)
+{
+ // check that giveback is working for DELIMITER ops
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_list";
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_DELIMITER);
+ ratelimit.giveback_tokens("GET", key, RES_DELIMITER, &info);
+ time = ceph::coarse_real_clock::now();
+ success = ratelimit.should_rate_limit("GET", key, time, &info, RES_DELIMITER);
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, accept_delimiter_op_after_refill)
+{
+ // check that tokens are being filled properly for DELIMITER ops
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_list";
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_DELIMITER);
+ time += 61s;
+ success = ratelimit.should_rate_limit("GET", key, time, &info, RES_DELIMITER);
+ EXPECT_EQ(false, success);
+}
+
+// LIST RES_PREFIX minimal tests
+TEST(RGWRateLimit, reject_prefix_op_over_limit)
+{
+ // check that PREFIX op is being rejected because there are not enough tokens
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_list";
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_PREFIX);
+ time = ceph::coarse_real_clock::now();
+ success = ratelimit.should_rate_limit("GET", key, time, &info, RES_PREFIX);
+ EXPECT_EQ(true, success);
+}
+
+TEST(RGWRateLimit, accept_prefix_op_after_giveback)
+{
+ // check that giveback is working for PREFIX ops
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_list";
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_PREFIX);
+ ratelimit.giveback_tokens("GET", key, RES_PREFIX, &info);
+ time = ceph::coarse_real_clock::now();
+ success = ratelimit.should_rate_limit("GET", key, time, &info, RES_PREFIX);
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, accept_prefix_op_after_refill)
+{
+ // check that tokens are being filled properly for PREFIX ops
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_list";
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, RES_PREFIX);
+ time += 61s;
+ success = ratelimit.should_rate_limit("GET", key, time, &info, RES_PREFIX);
+ EXPECT_EQ(false, success);
+}
+
+
+TEST(RGWRateLimitEntry, reject_list_op_over_limit)
+{
+ // check that LIST request is being rejected because there are not enough tokens
+ RGWRateLimitInfo info;
+ RateLimiterEntry entry;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ auto time = ceph::coarse_real_clock::now().time_since_epoch();
+ bool success = entry.should_rate_limit(OpType::List, &info, time);
+ time = ceph::coarse_real_clock::now().time_since_epoch();
+ success = entry.should_rate_limit(OpType::List, &info, time);
+ EXPECT_EQ(true, success);
+}
+
+TEST(RGWRateLimitEntry, accept_list_op_after_giveback)
+{
+ // check that giveback is working fine for LIST ops
+ RGWRateLimitInfo info;
+ RateLimiterEntry entry;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ auto time = ceph::coarse_real_clock::now().time_since_epoch();
+ bool success = entry.should_rate_limit(OpType::List, &info, time);
+ entry.giveback_tokens(OpType::List);
+ time = ceph::coarse_real_clock::now().time_since_epoch();
+ success = entry.should_rate_limit(OpType::List, &info, time);
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, accept_list_op_after_refill)
+{
+ // check that tokens are being filled properly for LIST ops
+ RateLimiterEntry entry;
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ auto time = ceph::coarse_real_clock::now().time_since_epoch();
+ bool success = entry.should_rate_limit(OpType::List, &info, time);
+ time += 61s;
+ success = entry.should_rate_limit(OpType::List, &info, time);
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, list_limit_does_not_affect_reads)
+{
+ // list limit does not affect reads
+ RateLimiterEntry entry;
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ info.max_read_ops = 1;
+ auto time = ceph::coarse_real_clock::now().time_since_epoch();
+ bool success = entry.should_rate_limit(OpType::List, &info, time);
+ time = ceph::coarse_real_clock::now().time_since_epoch();
+ success = entry.should_rate_limit(OpType::Read, &info, time);
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, read_limit_does_not_affect_lists)
+{
+ // read limit does not affect lists
+ RateLimiterEntry entry;
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ info.max_read_ops = 1;
+ auto time = ceph::coarse_real_clock::now().time_since_epoch();
+ bool success = entry.should_rate_limit(OpType::Read, &info, time);
+ time = ceph::coarse_real_clock::now().time_since_epoch();
+ success = entry.should_rate_limit(OpType::List, &info, time);
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, list_limit_does_not_affect_writes)
+{
+ // list limit does not affect writes
+ RateLimiterEntry entry;
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ info.max_write_ops = 1;
+ auto time = ceph::coarse_real_clock::now().time_since_epoch();
+ bool success = entry.should_rate_limit(OpType::List, &info, time);
+ time = ceph::coarse_real_clock::now().time_since_epoch();
+ success = entry.should_rate_limit(OpType::Write, &info, time);
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, write_limit_does_not_affect_lists)
+{
+ // write limit does not affect lists
+ RateLimiterEntry entry;
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ info.max_write_ops = 1;
+ auto time = ceph::coarse_real_clock::now().time_since_epoch();
+ bool success = entry.should_rate_limit(OpType::Write, &info, time);
+ time = ceph::coarse_real_clock::now().time_since_epoch();
+ success = entry.should_rate_limit(OpType::List, &info, time);
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, list_limit_does_not_affect_deletes)
+{
+ // list limit does not affect deletes
+ RateLimiterEntry entry;
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ info.max_delete_ops = 1;
+ auto time = ceph::coarse_real_clock::now().time_since_epoch();
+ bool success = entry.should_rate_limit(OpType::List, &info, time);
+ time = ceph::coarse_real_clock::now().time_since_epoch();
+ success = entry.should_rate_limit(OpType::Delete, &info, time);
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, delete_limit_does_not_affect_lists)
+{
+ // delete limit does not affect lists
+ RateLimiterEntry entry;
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_list_ops = 1;
+ info.max_delete_ops = 1;
+ auto time = ceph::coarse_real_clock::now().time_since_epoch();
+ bool success = entry.should_rate_limit(OpType::Delete, &info, time);
+ time = ceph::coarse_real_clock::now().time_since_epoch();
+ success = entry.should_rate_limit(OpType::List, &info, time);
+ EXPECT_EQ(false, success);
+}
+
+
+TEST(RGWRateLimit, reject_delete_op_over_limit)
+{
+ // check that DELETE op is being rejected because there are not enough tokens
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_delete_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_delete";
+ bool success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+ time = ceph::coarse_real_clock::now();
+ success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+ EXPECT_EQ(true, success);
+}
+
+TEST(RGWRateLimit, accept_delete_op_after_giveback)
+{
+ // check that giveback is working for DELETE ops
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_delete_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_delete";
+ bool success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+ ratelimit.giveback_tokens("DELETE", key, "", &info);
+ time = ceph::coarse_real_clock::now();
+ success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, accept_delete_op_after_refill)
+{
+ // check that tokens are being filled properly for DELETE ops
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_delete_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_delete";
+ bool success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+ time += 61s;
+ success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, delete_limit_does_not_affect_reads)
+{
+ // delete limit does not affect reads
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_delete_ops = 1;
+ info.max_read_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_delete";
+ bool success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+ // Should still be able to do a normal GET (read)
+ success = ratelimit.should_rate_limit("GET", key, time, &info, "");
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, read_limit_does_not_affect_deletes)
+{
+ // read limit does not affect deletes
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_delete_ops = 1;
+ info.max_read_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_delete";
+ bool success = ratelimit.should_rate_limit("GET", key, time, &info, "");
+ // Should still be able to do a DELETE op
+ success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, write_limit_does_not_affect_deletes)
+{
+ // write limit does not affect deletes
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_delete_ops = 1;
+ info.max_write_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_delete";
+ bool success = ratelimit.should_rate_limit("PUT", key, time, &info, "");
+ // Should still be able to do a DELETE op
+ success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimit, delete_limit_does_not_affect_writes)
+{
+ // delete limit does not affect writes
+ std::atomic_bool replacing;
+ std::condition_variable cv;
+ RateLimiter ratelimit(g_ceph_context, replacing, cv);
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_delete_ops = 1;
+ info.max_write_ops = 1;
+ auto time = ceph::coarse_real_clock::now();
+ std::string key = "uuser_delete";
+ bool success = ratelimit.should_rate_limit("DELETE", key, time, &info, "");
+ // Should still be able to do a PUT (write)
+ success = ratelimit.should_rate_limit("PUT", key, time, &info, "");
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, reject_delete_op_over_limit)
+{
+ // check that DELETE request is being rejected because there are not enough tokens
+ RGWRateLimitInfo info;
+ RateLimiterEntry entry;
+ info.enabled = true;
+ info.max_delete_ops = 1;
+ auto time = ceph::coarse_real_clock::now().time_since_epoch();
+ bool success = entry.should_rate_limit(OpType::Delete, &info, time);
+ time = ceph::coarse_real_clock::now().time_since_epoch();
+ success = entry.should_rate_limit(OpType::Delete, &info, time);
+ EXPECT_EQ(true, success);
+}
+
+TEST(RGWRateLimitEntry, accept_delete_op_after_giveback)
+{
+ // check that giveback is working fine for DELETE ops
+ RGWRateLimitInfo info;
+ RateLimiterEntry entry;
+ info.enabled = true;
+ info.max_delete_ops = 1;
+ auto time = ceph::coarse_real_clock::now().time_since_epoch();
+ bool success = entry.should_rate_limit(OpType::Delete, &info, time);
+ entry.giveback_tokens(OpType::Delete);
+ time = ceph::coarse_real_clock::now().time_since_epoch();
+ success = entry.should_rate_limit(OpType::Delete, &info, time);
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, accept_delete_op_after_refill)
+{
+ // check that tokens are being filled properly for DELETE ops
+ RateLimiterEntry entry;
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_delete_ops = 1;
+ auto time = ceph::coarse_real_clock::now().time_since_epoch();
+ bool success = entry.should_rate_limit(OpType::Delete, &info, time);
+ time += 61s;
+ success = entry.should_rate_limit(OpType::Delete, &info, time);
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, delete_limit_does_not_affect_reads)
+{
+ // delete limit does not affect reads
+ RateLimiterEntry entry;
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_delete_ops = 1;
+ info.max_read_ops = 1;
+ auto time = ceph::coarse_real_clock::now().time_since_epoch();
+ bool success = entry.should_rate_limit(OpType::Delete, &info, time);
+ time = ceph::coarse_real_clock::now().time_since_epoch();
+ success = entry.should_rate_limit(OpType::Read, &info, time);
EXPECT_EQ(false, success);
}
+
+TEST(RGWRateLimitEntry, read_limit_does_not_affect_deletes)
+{
+ // read limit does not affect deletes
+ RateLimiterEntry entry;
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_delete_ops = 1;
+ info.max_read_ops = 1;
+ auto time = ceph::coarse_real_clock::now().time_since_epoch();
+ bool success = entry.should_rate_limit(OpType::Read, &info, time);
+ time = ceph::coarse_real_clock::now().time_since_epoch();
+ success = entry.should_rate_limit(OpType::Delete, &info, time);
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, write_limit_does_not_affect_deletes)
+{
+ // write limit does not affect deletes
+ RateLimiterEntry entry;
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_delete_ops = 1;
+ info.max_write_ops = 1;
+ auto time = ceph::coarse_real_clock::now().time_since_epoch();
+ bool success = entry.should_rate_limit(OpType::Write, &info, time);
+ time = ceph::coarse_real_clock::now().time_since_epoch();
+ success = entry.should_rate_limit(OpType::Delete, &info, time);
+ EXPECT_EQ(false, success);
+}
+
+TEST(RGWRateLimitEntry, delete_limit_does_not_affect_writes)
+{
+ // delete limit does not affect writes
+ RateLimiterEntry entry;
+ RGWRateLimitInfo info;
+ info.enabled = true;
+ info.max_delete_ops = 1;
+ info.max_write_ops = 1;
+ auto time = ceph::coarse_real_clock::now().time_since_epoch();
+ bool success = entry.should_rate_limit(OpType::Delete, &info, time);
+ time = ceph::coarse_real_clock::now().time_since_epoch();
+ success = entry.should_rate_limit(OpType::Write, &info, time);
+ EXPECT_EQ(false, success);
+}
+