mirror of
https://github.com/google/nomulus.git
synced 2025-05-14 08:27:14 +02:00
Add ability to configure proxy quotas
The quotas can be configured in the yaml configuration file. Default quota will be applied to any userId that is not matched in the custom quota list. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=178804649
This commit is contained in:
parent
c941190e71
commit
c5515ab4e6
8 changed files with 258 additions and 1 deletions
|
@ -61,6 +61,7 @@ public class ProxyConfig {
|
||||||
public int headerLengthBytes;
|
public int headerLengthBytes;
|
||||||
public int readTimeoutSeconds;
|
public int readTimeoutSeconds;
|
||||||
public String serverHostname;
|
public String serverHostname;
|
||||||
|
public Quota quota;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Configuration options that apply to WHOIS protocol. */
|
/** Configuration options that apply to WHOIS protocol. */
|
||||||
|
@ -70,6 +71,7 @@ public class ProxyConfig {
|
||||||
public String relayPath;
|
public String relayPath;
|
||||||
public int maxMessageLengthBytes;
|
public int maxMessageLengthBytes;
|
||||||
public int readTimeoutSeconds;
|
public int readTimeoutSeconds;
|
||||||
|
public Quota quota;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Configuration options that apply to GCP load balancer health check protocol. */
|
/** Configuration options that apply to GCP load balancer health check protocol. */
|
||||||
|
@ -92,6 +94,21 @@ public class ProxyConfig {
|
||||||
public int writeIntervalSeconds;
|
public int writeIntervalSeconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Configuration options that apply to quota management. */
|
||||||
|
public static class Quota {
|
||||||
|
|
||||||
|
/** Quota configuration for a specific set of users. */
|
||||||
|
public static class QuotaGroup {
|
||||||
|
public List<String> userId;
|
||||||
|
public int tokenAmount;
|
||||||
|
public int refillSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int refreshSeconds;
|
||||||
|
public QuotaGroup defaultQuota;
|
||||||
|
public List<QuotaGroup> customQuota;
|
||||||
|
}
|
||||||
|
|
||||||
static ProxyConfig getProxyConfig(Environment env) {
|
static ProxyConfig getProxyConfig(Environment env) {
|
||||||
String defaultYaml = readResourceUtf8(ProxyConfig.class, DEFAULT_CONFIG);
|
String defaultYaml = readResourceUtf8(ProxyConfig.class, DEFAULT_CONFIG);
|
||||||
String customYaml =
|
String customYaml =
|
||||||
|
|
|
@ -82,6 +82,41 @@ epp:
|
||||||
# TODO(b/64510444) Remove this after nomulus no longer check sni header.
|
# TODO(b/64510444) Remove this after nomulus no longer check sni header.
|
||||||
serverHostname: epp.yourdomain.tld
|
serverHostname: epp.yourdomain.tld
|
||||||
|
|
||||||
|
# Quota configuration for EPP
|
||||||
|
quota:
|
||||||
|
|
||||||
|
# Token database refresh period. Set to 0 to disable refresh.
|
||||||
|
#
|
||||||
|
# After the set time period, inactive userIds will be deleted.
|
||||||
|
refreshSeconds: 0
|
||||||
|
|
||||||
|
# Default quota for any userId not matched in customQuota.
|
||||||
|
defaultQuota:
|
||||||
|
|
||||||
|
# List of identifiers, e. g. IP address, certificate hash.
|
||||||
|
#
|
||||||
|
# userId for defaultQuota should always be an empty list. Any value
|
||||||
|
# in the list will be discarded.
|
||||||
|
#
|
||||||
|
# There should be no duplicate userIds, either within this list, or
|
||||||
|
# across quota groups within customQuota. Any duplication will result
|
||||||
|
# in an error when constructing QuotaConfig.
|
||||||
|
userId: []
|
||||||
|
|
||||||
|
# Number of tokens allotted to the matched user. Set to -1 to allow
|
||||||
|
# infinite quota.
|
||||||
|
tokenAmount: 100
|
||||||
|
|
||||||
|
# Token refill period. Set to 0 to disable refill.
|
||||||
|
#
|
||||||
|
# After the set time period, the token for the user will be
|
||||||
|
# reset to tokenAmount.
|
||||||
|
refillSeconds: 0
|
||||||
|
|
||||||
|
# List of custom quotas for specific userId. Use the same schema as
|
||||||
|
# defaultQuota for list entries.
|
||||||
|
customQuota: []
|
||||||
|
|
||||||
whois:
|
whois:
|
||||||
port: 43
|
port: 43
|
||||||
relayHost: registry-project-id.appspot.com
|
relayHost: registry-project-id.appspot.com
|
||||||
|
@ -100,6 +135,36 @@ whois:
|
||||||
# idle connection.
|
# idle connection.
|
||||||
readTimeoutSeconds: 60
|
readTimeoutSeconds: 60
|
||||||
|
|
||||||
|
# Quota configuration for WHOIS
|
||||||
|
quota:
|
||||||
|
|
||||||
|
# Token database refresh period. Set to 0 to disable refresh.
|
||||||
|
#
|
||||||
|
# After the set time period, inactive token buckets will be deleted.
|
||||||
|
refreshSeconds: 3600
|
||||||
|
|
||||||
|
# Default quota for any userId not matched in customQuota.
|
||||||
|
defaultQuota:
|
||||||
|
|
||||||
|
# List of identifiers, e. g. IP address, certificate hash.
|
||||||
|
#
|
||||||
|
# userId for defaultQuota should always be an empty list.
|
||||||
|
userId: []
|
||||||
|
|
||||||
|
# Number of tokens allotted to the matched user. Set to -1 to allow
|
||||||
|
# infinite quota.
|
||||||
|
tokenAmount: 100
|
||||||
|
|
||||||
|
# Token refill period. Set to 0 to disable refill.
|
||||||
|
#
|
||||||
|
# After the set time period, the token for the given user will be
|
||||||
|
# reset to tokenAmount.
|
||||||
|
refillSeconds: 600
|
||||||
|
|
||||||
|
# List of custom quotas for specific userId. Use the same schema as
|
||||||
|
# defaultQuota for list entries.
|
||||||
|
customQuota: []
|
||||||
|
|
||||||
healthCheck:
|
healthCheck:
|
||||||
port: 11111
|
port: 11111
|
||||||
|
|
||||||
|
|
64
java/google/registry/proxy/quota/QuotaConfig.java
Normal file
64
java/google/registry/proxy/quota/QuotaConfig.java
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package google.registry.proxy.quota;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import google.registry.proxy.ProxyConfig.Quota;
|
||||||
|
import google.registry.proxy.ProxyConfig.Quota.QuotaGroup;
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
/** Value class that stores the quota configuration for a protocol. */
|
||||||
|
public class QuotaConfig {
|
||||||
|
|
||||||
|
private final int refreshSeconds;
|
||||||
|
private final QuotaGroup defaultQuota;
|
||||||
|
private final ImmutableMap<String, QuotaGroup> customQuotaMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@link QuotaConfig} from a {@link Quota}.
|
||||||
|
*
|
||||||
|
* <p>Each {@link QuotaGroup} is keyed to all the {@code userId}s it contains. This allows for
|
||||||
|
* fast lookup with a {@code userId}.
|
||||||
|
*/
|
||||||
|
public QuotaConfig(Quota quota) {
|
||||||
|
refreshSeconds = quota.refreshSeconds;
|
||||||
|
defaultQuota = quota.defaultQuota;
|
||||||
|
ImmutableMap.Builder<String, QuotaGroup> mapBuilder = new ImmutableMap.Builder<>();
|
||||||
|
quota.customQuota.forEach(
|
||||||
|
quotaGroup -> quotaGroup.userId.forEach(userId -> mapBuilder.put(userId, quotaGroup)));
|
||||||
|
customQuotaMap = mapBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
QuotaGroup findQuotaGroup(String userId) {
|
||||||
|
return customQuotaMap.getOrDefault(userId, defaultQuota);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the token amount for the given {@code userId}. */
|
||||||
|
public int getTokenAmount(String userId) {
|
||||||
|
return findQuotaGroup(userId).tokenAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the refill period for the given {@code userId}. */
|
||||||
|
public Duration getRefillPeriod(String userId) {
|
||||||
|
return Duration.standardSeconds(findQuotaGroup(userId).refillSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the refresh period for this quota config. */
|
||||||
|
public Duration getRefreshPeriod() {
|
||||||
|
return Duration.standardSeconds(refreshSeconds);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,10 @@ load("//java/com/google/testing/builddefs:GenTestRules.bzl", "GenTestRules")
|
||||||
java_library(
|
java_library(
|
||||||
name = "proxy",
|
name = "proxy",
|
||||||
srcs = glob(["**/*.java"]),
|
srcs = glob(["**/*.java"]),
|
||||||
resources = glob(["testdata/*.xml"]),
|
resources = glob([
|
||||||
|
"testdata/*.xml",
|
||||||
|
"quota/testdata/*.yaml",
|
||||||
|
]),
|
||||||
deps = [
|
deps = [
|
||||||
"//java/google/registry/monitoring/metrics",
|
"//java/google/registry/monitoring/metrics",
|
||||||
"//java/google/registry/monitoring/metrics/contrib",
|
"//java/google/registry/monitoring/metrics/contrib",
|
||||||
|
@ -32,6 +35,7 @@ java_library(
|
||||||
"@junit",
|
"@junit",
|
||||||
"@org_bouncycastle_bcpkix_jdk15on",
|
"@org_bouncycastle_bcpkix_jdk15on",
|
||||||
"@org_mockito_all",
|
"@org_mockito_all",
|
||||||
|
"@org_yaml_snakeyaml",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
71
javatests/google/registry/proxy/quota/QuotaConfigTest.java
Normal file
71
javatests/google/registry/proxy/quota/QuotaConfigTest.java
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package google.registry.proxy.quota;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static google.registry.testing.JUnitBackports.expectThrows;
|
||||||
|
import static google.registry.util.ResourceUtils.readResourceUtf8;
|
||||||
|
|
||||||
|
import google.registry.proxy.ProxyConfig.Quota;
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
/** Unit Tests for {@link QuotaConfig} */
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class QuotaConfigTest {
|
||||||
|
|
||||||
|
private QuotaConfig quotaConfig;
|
||||||
|
|
||||||
|
private static QuotaConfig loadQuotaConfig(String filename) {
|
||||||
|
return new QuotaConfig(
|
||||||
|
new Yaml()
|
||||||
|
.loadAs(readResourceUtf8(QuotaConfigTest.class, "testdata/" + filename), Quota.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateQuota(String userId, int tokenAmount, int refillSeconds) {
|
||||||
|
assertThat(quotaConfig.getTokenAmount(userId)).isEqualTo(tokenAmount);
|
||||||
|
assertThat(quotaConfig.getRefillPeriod(userId))
|
||||||
|
.isEqualTo(Duration.standardSeconds(refillSeconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSuccess_regularConfig() {
|
||||||
|
quotaConfig = loadQuotaConfig("quota_config_regular.yaml");
|
||||||
|
assertThat(quotaConfig.getRefreshPeriod()).isEqualTo(Duration.standardSeconds(3600));
|
||||||
|
validateQuota("abc", 10, 60);
|
||||||
|
validateQuota("987lol", 500, 10);
|
||||||
|
validateQuota("no_match", 100, 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSuccess_onlyDefault() {
|
||||||
|
quotaConfig = loadQuotaConfig("quota_config_default.yaml");
|
||||||
|
assertThat(quotaConfig.getRefreshPeriod()).isEqualTo(Duration.standardSeconds(3600));
|
||||||
|
validateQuota("abc", 100, 60);
|
||||||
|
validateQuota("987lol", 100, 60);
|
||||||
|
validateQuota("no_match", 100, 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFailure_duplicateUserId() {
|
||||||
|
IllegalArgumentException e =
|
||||||
|
expectThrows(
|
||||||
|
IllegalArgumentException.class, () -> loadQuotaConfig("quota_config_duplicate.yaml"));
|
||||||
|
assertThat(e).hasMessageThat().contains("Multiple entries with same key");
|
||||||
|
}
|
||||||
|
}
|
8
javatests/google/registry/proxy/quota/testdata/quota_config_default.yaml
vendored
Normal file
8
javatests/google/registry/proxy/quota/testdata/quota_config_default.yaml
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
refreshSeconds: 3600
|
||||||
|
|
||||||
|
defaultQuota:
|
||||||
|
userId: []
|
||||||
|
tokenAmount: 100
|
||||||
|
refillSeconds: 60
|
||||||
|
|
||||||
|
customQuota: []
|
14
javatests/google/registry/proxy/quota/testdata/quota_config_duplicate.yaml
vendored
Normal file
14
javatests/google/registry/proxy/quota/testdata/quota_config_duplicate.yaml
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
refreshSeconds: 3600
|
||||||
|
|
||||||
|
defaultQuota:
|
||||||
|
userId: []
|
||||||
|
tokenAmount: 100
|
||||||
|
refillSeconds: 60
|
||||||
|
|
||||||
|
customQuota:
|
||||||
|
- userId: ["abc", "def"]
|
||||||
|
tokenAmount: 10
|
||||||
|
refillSeconds: 60
|
||||||
|
- userId: ["xyz123", "def", "luckycat"]
|
||||||
|
tokenAmount: 500
|
||||||
|
refillSeconds: 10
|
14
javatests/google/registry/proxy/quota/testdata/quota_config_regular.yaml
vendored
Normal file
14
javatests/google/registry/proxy/quota/testdata/quota_config_regular.yaml
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
refreshSeconds: 3600
|
||||||
|
|
||||||
|
defaultQuota:
|
||||||
|
userId: []
|
||||||
|
tokenAmount: 100
|
||||||
|
refillSeconds: 60
|
||||||
|
|
||||||
|
customQuota:
|
||||||
|
- userId: ["abc", "def"]
|
||||||
|
tokenAmount: 10
|
||||||
|
refillSeconds: 60
|
||||||
|
- userId: ["xyz123", "987lol", "luckycat"]
|
||||||
|
tokenAmount: 500
|
||||||
|
refillSeconds: 10
|
Loading…
Add table
Add a link
Reference in a new issue