Skip to content

Commit

Permalink
Add Request Rate-Limiter interfaces (Netflix#395)
Browse files Browse the repository at this point in the history
  • Loading branch information
raveeram authored Jul 14, 2020
1 parent aef2a1d commit 8c3f114
Show file tree
Hide file tree
Showing 12 changed files with 324 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
*
* Copyright 2020 Netflix, Inc.
*
* 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 com.netflix.metacat.common.server.api.ratelimiter;

/**
* Default RateLimiter service implementation.
*/
public class DefaultRateLimiter implements RateLimiter {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
*
* Copyright 2020 Netflix, Inc.
*
* 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 com.netflix.metacat.common.server.api.ratelimiter;

import lombok.NonNull;

/**
* Request rate-limiter service API.
*
* @author rveeramacheneni
*/
public interface RateLimiter {

/**
* Whether a given request has exceeded
* the pre-configured API request rate-limit.
*
* @param context The rate-limiter request context.
* @return True if it has exceeded the API request rate-limit.
*/
default boolean hasExceededRequestLimit(@NonNull RateLimiterRequestContext context) {
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
*
* Copyright 2020 Netflix, Inc.
*
* 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 com.netflix.metacat.common.server.api.ratelimiter;

import com.netflix.metacat.common.QualifiedName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

/**
* Context pertaining to a rate-limited API request.
*
* @author rveeramacheneni
*/
@AllArgsConstructor
@Builder
@Getter
public class RateLimiterRequestContext {
/**
* The API request. eg. getTable, updateTable etc.
*/
private String requestName;

/**
* The QualifiedName of the resource.
*/
private QualifiedName name;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
*
* Copyright 2020 Netflix, Inc.
*
* 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.
*
*/

/**
* Rate limiter service interfaces.
*/
package com.netflix.metacat.common.server.api.ratelimiter;
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public enum Metrics {
CounterCatalogTraversalDatabaseReadFailed(Component.server, Type.counter, "catalogTraversalDatabaseReadFailed"),
CounterCatalogTraversalTableReadFailed(Component.server, Type.counter, "catalogTraversalTableReadFailed"),
CounterTableUpdateIgnoredException(Component.tableservice, Type.counter, "tableUpdateIgnoredException"),
CounterRequestRateLimitExceededCount(Component.server, Type.counter, "requestRateLimitExceeded"),

/**
* Notifications.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -470,5 +470,20 @@ public interface Config {
* @return set of tags
*/
Set<String> getNoTableRenameOnTags();

/**
* Whether the rate limiter is enabled.
*
* @return True if it is.
*/
boolean isRateLimiterEnabled();

/**
* Whether the rate limiter is enforced
* and rejecting requests.
*
* @return True if it is.
*/
boolean isRateLimiterEnforced();
}

Original file line number Diff line number Diff line change
Expand Up @@ -549,4 +549,24 @@ public Set<String> getNoTableDeleteOnTags() {
public Set<String> getNoTableRenameOnTags() {
return this.metacatProperties.getTable().getRename().getNoRenameOnTagsSet();
}

/**
* Whether the rate limiter is enabled.
*
* @return True if it is.
*/
@Override
public boolean isRateLimiterEnabled() {
return this.metacatProperties.getRateLimiterProperties().isEnabled();
}

/**
* Whether the rate limiter is being enforced
* and rejecting requests.
*
* @return True if it is.
*/
public boolean isRateLimiterEnforced() {
return this.metacatProperties.getRateLimiterProperties().isEnforced();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,6 @@ public class MetacatProperties {
private AuthorizationServiceProperties authorization = new AuthorizationServiceProperties();
@NonNull
private AliasServiceProperties aliasServiceProperties = new AliasServiceProperties();
@NonNull
private RateLimiterProperties rateLimiterProperties = new RateLimiterProperties();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.netflix.metacat.common.server.properties;

import lombok.Data;

/**
* RateLimiter service properties.
*
* @author rveeramacheneni
*/
@Data
public class RateLimiterProperties {
private boolean enabled;
private boolean enforced;
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import com.netflix.metacat.common.exception.MetacatUnAuthorizedException;
import com.netflix.metacat.common.exception.MetacatUserMetadataException;
import com.netflix.metacat.common.exception.MetacatTooManyRequestsException;
import com.netflix.metacat.common.server.api.ratelimiter.RateLimiter;
import com.netflix.metacat.common.server.api.ratelimiter.RateLimiterRequestContext;
import com.netflix.metacat.common.server.connectors.exception.ConnectorException;
import com.netflix.metacat.common.server.connectors.exception.DatabaseAlreadyExistsException;
import com.netflix.metacat.common.server.connectors.exception.InvalidMetaException;
Expand Down Expand Up @@ -65,29 +67,35 @@ public final class RequestWrapper {
private final Registry registry;
private final Config config;
private final AliasService aliasService;
private final RateLimiter rateLimiter;

//Metrics
private final Id requestCounterId;
private final Id requestFailureCounterId;
private final Id requestTimerId;
private final Id requestRateLimitExceededId;

/**
* Wrapper class for processing the request.
*
* @param registry registry
* @param config Config
* @param aliasService AliasService
* @param rateLimiter RateLimiter
*/
@Autowired
public RequestWrapper(@NotNull @NonNull final Registry registry,
@NotNull @NonNull final Config config,
@NotNull @NonNull final AliasService aliasService) {
@NotNull @NonNull final AliasService aliasService,
@NotNull @NonNull final RateLimiter rateLimiter) {
this.registry = registry;
this.config = config;
this.aliasService = aliasService;
this.rateLimiter = rateLimiter;
requestCounterId = registry.createId(Metrics.CounterRequestCount.getMetricName());
requestFailureCounterId = registry.createId(Metrics.CounterRequestFailureCount.getMetricName());
requestTimerId = registry.createId(Metrics.TimerRequest.getMetricName());
requestRateLimitExceededId = registry.createId(Metrics.CounterRequestRateLimitExceededCount.getMetricName());
}

/**
Expand Down Expand Up @@ -126,6 +134,7 @@ public <R> R processRequest(
final Map<String, String> tags = new HashMap<>(name.parts());
tags.put("request", resourceRequestName);
registry.counter(requestCounterId.withTags(tags)).increment();
checkRequestRateLimit(name, resourceRequestName, tags);

try {
log.info("### Calling method: {} for {}", resourceRequestName, name);
Expand Down Expand Up @@ -237,4 +246,26 @@ private void collectRequestExceptionMetrics(final Map<String, String> tags, fina
registry.counter(requestFailureCounterId.withTags(tags)).increment();
}

private void checkRequestRateLimit(@NonNull final QualifiedName name,
@NonNull final String resourceRequestName,
@NonNull final Map<String, String> tags) {
if (!this.config.isRateLimiterEnabled()) {
return;
}
log.info("Checking request rate limit for {}. Request: {}",
name, resourceRequestName);

if (this.rateLimiter.hasExceededRequestLimit(new RateLimiterRequestContext(resourceRequestName, name))) {
final String errorMsg = String.format("Too many requests for resource %s. Request: %s",
name, resourceRequestName);
log.warn(errorMsg);
registry.counter(requestRateLimitExceededId.withTags(tags)).increment();
if (this.config.isRateLimiterEnforced()) {
final MetacatTooManyRequestsException ex =
new MetacatTooManyRequestsException(errorMsg);
this.collectRequestExceptionMetrics(tags, ex.getClass().getSimpleName());
throw ex;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
*/
package com.netflix.metacat.main.configs;

import com.netflix.metacat.common.server.api.ratelimiter.DefaultRateLimiter;
import com.netflix.metacat.common.server.api.ratelimiter.RateLimiter;
import com.netflix.metacat.common.server.converter.ConverterUtil;
import com.netflix.metacat.common.server.events.MetacatEventBus;
import com.netflix.metacat.common.server.properties.Config;
Expand Down Expand Up @@ -90,6 +92,7 @@ public TagService tagService() {

/**
* Authorization service.
*
* @param config metacat config
* @return authorization class based on config
*/
Expand All @@ -103,6 +106,7 @@ public AuthorizationService authorizationService(

/**
* Alias service.
*
* @return an instance of the Alias service.
*/
@Bean
Expand All @@ -122,6 +126,17 @@ public LookupService lookupService() {
return new DefaultLookupService();
}

/**
* RateLimiter service.
*
* @return The rate-limiter service bean.
*/
@Bean
@ConditionalOnMissingBean(RateLimiter.class)
public RateLimiter rateLimiter() {
return new DefaultRateLimiter();
}

/**
* The catalog service bean.
*
Expand All @@ -144,12 +159,12 @@ public CatalogService catalogService(
/**
* The database service bean.
*
* @param connectorManager Connector manager to use
* @param userMetadataService User metadata service to use
* @param metacatEventBus Event bus to use
* @param converterUtil Converter utilities
* @param catalogService The catalog service to use
* @param authorizationService authorization Service
* @param connectorManager Connector manager to use
* @param userMetadataService User metadata service to use
* @param metacatEventBus Event bus to use
* @param converterUtil Converter utilities
* @param catalogService The catalog service to use
* @param authorizationService authorization Service
* @return Catalog service implementation
*/
@Bean
Expand All @@ -174,7 +189,7 @@ public DatabaseService databaseService(
/**
* The table service bean.
*
* @param connectorManager Connector manager to use
* @param connectorManager Connector manager to use
* @param connectorTableServiceProxy connector table service proxy
* @param databaseService database service
* @param tagService tag service
Expand Down Expand Up @@ -377,9 +392,9 @@ public MetacatInitializationService metacatInitializationService(
/**
* The catalog traversal service helper.
*
* @param catalogService Catalog service
* @param databaseService Database service
* @param tableService Table service
* @param catalogService Catalog service
* @param databaseService Database service
* @param tableService Table service
* @return The catalog traversal service helper bean
*/
@Bean
Expand Down
Loading

0 comments on commit 8c3f114

Please sign in to comment.