34
34
import io .grpc .ServerCallHandler ;
35
35
import io .grpc .ServerInterceptor ;
36
36
import io .grpc .internal .GrpcUtil ;
37
- import io .grpc .xds . Filter . ServerInterceptorBuilder ;
37
+ import io .grpc .internal . SharedResourceHolder ;
38
38
import io .grpc .xds .client .XdsLogger ;
39
39
import io .grpc .xds .client .XdsLogger .XdsLogLevel ;
40
40
import io .grpc .xds .internal .datatype .GrpcService ;
46
46
import io .grpc .xds .internal .rlqs .RlqsCache ;
47
47
import io .grpc .xds .internal .rlqs .RlqsFilterState ;
48
48
import io .grpc .xds .internal .rlqs .RlqsRateLimitResult ;
49
+ import java .util .ConcurrentModificationException ;
49
50
import java .util .concurrent .ScheduledExecutorService ;
51
+ import java .util .concurrent .atomic .AtomicBoolean ;
50
52
import java .util .concurrent .atomic .AtomicReference ;
53
+ import java .util .logging .Logger ;
51
54
import javax .annotation .Nullable ;
52
55
53
56
/** RBAC Http filter implementation. */
54
57
// TODO(sergiitk): introduce a layer between the filter and interceptor.
55
58
// lds has filter names and the names are unique - even for server instances.
56
- final class RlqsFilter implements Filter , ServerInterceptorBuilder {
59
+ final class RlqsFilter implements Filter {
57
60
private final XdsLogger logger ;
58
61
59
62
static final boolean enabled = GrpcUtil .getFlag ("GRPC_EXPERIMENTAL_XDS_ENABLE_RLQS" , false );
@@ -62,70 +65,124 @@ final class RlqsFilter implements Filter, ServerInterceptorBuilder {
62
65
// Do do not fail on parsing errors, only log requests.
63
66
static final boolean dryRun = GrpcUtil .getFlag ("GRPC_EXPERIMENTAL_RLQS_DRY_RUN" , false );
64
67
65
- static final RlqsFilter INSTANCE = new RlqsFilter ();
66
-
67
68
static final String TYPE_URL = "type.googleapis.com/"
68
69
+ "envoy.extensions.filters.http.rate_limit_quota.v3.RateLimitQuotaFilterConfig" ;
69
70
static final String TYPE_URL_OVERRIDE_CONFIG = "type.googleapis.com/"
70
71
+ "envoy.extensions.filters.http.rate_limit_quota.v3.RateLimitQuotaOverride" ;
71
72
73
+ private final AtomicBoolean shutdown = new AtomicBoolean ();
72
74
private final AtomicReference <RlqsCache > rlqsCache = new AtomicReference <>();
73
75
74
- public RlqsFilter () {
75
- // TODO(sergiitk): one per new instance when filters are refactored.
76
+ // TODO(sergiitk): [IMPL] figure out what to use here.
77
+ private final ScheduledExecutorService scheduler =
78
+ SharedResourceHolder .get (GrpcUtil .TIMER_SERVICE );
79
+
80
+ public RlqsFilter (String name ) {
76
81
logger = XdsLogger .withLogId (InternalLogId .allocate (this .getClass (), null ));
77
82
logger .log (XdsLogLevel .DEBUG ,
78
- "Created RLQS Filter with enabled=" + enabled + " , dryRun=" + dryRun );
83
+ "Created RLQS Filter name='%s' with enabled=%s , dryRun=%s" , name , enabled , dryRun );
79
84
}
80
85
81
- @ Override
82
- public String [] typeUrls () {
83
- return new String []{TYPE_URL , TYPE_URL_OVERRIDE_CONFIG };
84
- }
86
+ static final class Provider implements Filter .Provider {
87
+ private static final Logger logger = Logger .getLogger (Provider .class .getName ());
85
88
86
- @ Override
87
- public boolean isEnabled () {
88
- return enabled ;
89
- }
89
+ @ Override
90
+ public String [] typeUrls () {
91
+ return new String []{ TYPE_URL , TYPE_URL_OVERRIDE_CONFIG } ;
92
+ }
90
93
91
- @ Override
92
- public ConfigOrError <RlqsFilterConfig > parseFilterConfig (Message rawProtoMessage ) {
93
- try {
94
- RlqsFilterConfig rlqsFilterConfig =
95
- parseRlqsFilter (unpackAny (rawProtoMessage , RateLimitQuotaFilterConfig .class ));
96
- return ConfigOrError .fromConfig (rlqsFilterConfig );
97
- } catch (InvalidProtocolBufferException e ) {
98
- return ConfigOrError .fromError ("Can't unpack RateLimitQuotaFilterConfig proto: " + e );
99
- } catch (ResourceInvalidException e ) {
100
- return ConfigOrError .fromError (e .getMessage ());
94
+ @ Override
95
+ public boolean isServerFilter () {
96
+ return true ;
97
+ }
98
+
99
+ @ Override
100
+ public RlqsFilter newInstance (String name ) {
101
+ return new RlqsFilter (name );
102
+ }
103
+
104
+ @ Override
105
+ public ConfigOrError <RlqsFilterConfig > parseFilterConfig (Message rawProtoMessage ) {
106
+ try {
107
+ RlqsFilterConfig rlqsFilterConfig =
108
+ parseRlqsFilter (unpackAny (rawProtoMessage , RateLimitQuotaFilterConfig .class ));
109
+ return ConfigOrError .fromConfig (rlqsFilterConfig );
110
+ } catch (InvalidProtocolBufferException e ) {
111
+ return ConfigOrError .fromError ("Can't unpack RateLimitQuotaFilterConfig proto: " + e );
112
+ } catch (ResourceInvalidException e ) {
113
+ return ConfigOrError .fromError (e .getMessage ());
114
+ }
115
+ }
116
+
117
+ @ Override
118
+ public ConfigOrError <RlqsFilterConfig > parseFilterConfigOverride (Message rawProtoMessage ) {
119
+ try {
120
+ RlqsFilterConfig rlqsFilterConfig =
121
+ parseRlqsFilterOverride (unpackAny (rawProtoMessage , RateLimitQuotaOverride .class ));
122
+ return ConfigOrError .fromConfig (rlqsFilterConfig );
123
+ } catch (InvalidProtocolBufferException e ) {
124
+ return ConfigOrError .fromError ("Can't unpack RateLimitQuotaOverride proto: " + e );
125
+ } catch (ResourceInvalidException e ) {
126
+ return ConfigOrError .fromError (e .getMessage ());
127
+ }
128
+ }
129
+
130
+ @ VisibleForTesting
131
+ RlqsFilterConfig parseRlqsFilter (RateLimitQuotaFilterConfig rlqsFilterProto )
132
+ throws ResourceInvalidException , InvalidProtocolBufferException {
133
+ RlqsFilterConfig .Builder builder = RlqsFilterConfig .builder ();
134
+ if (rlqsFilterProto .getDomain ().isEmpty ()) {
135
+ throw new ResourceInvalidException ("RateLimitQuotaFilterConfig domain is required" );
136
+ }
137
+ builder .domain (rlqsFilterProto .getDomain ())
138
+ .rlqsService (GrpcService .fromEnvoyProto (rlqsFilterProto .getRlqsServer ()));
139
+
140
+ // TODO(sergiitk): [IMPL] Remove
141
+ if (dryRun ) {
142
+ logger .finest ("RLQS DRY RUN: not parsing matchers" );
143
+ return builder .build ();
144
+ }
145
+
146
+ // TODO(sergiitk): [IMPL] actually parse, move to RlqsBucketSettings.fromProto()
147
+ RateLimitQuotaBucketSettings fallbackBucketSettingsProto = unpackAny (
148
+ rlqsFilterProto .getBucketMatchers ().getOnNoMatch ().getAction ().getTypedConfig (),
149
+ RateLimitQuotaBucketSettings .class );
150
+ RlqsBucketSettings fallbackBucket = RlqsBucketSettings .create (
151
+ ImmutableMap .of ("bucket_id" , headers -> "hello" ),
152
+ fallbackBucketSettingsProto .getReportingInterval ());
153
+
154
+ // TODO(sergiitk): [IMPL] actually parse, move to Matcher.fromProto()
155
+ Matcher <HttpMatchInput , RlqsBucketSettings > bucketMatchers = new RlqsMatcher (fallbackBucket );
156
+
157
+ return builder .bucketMatchers (bucketMatchers ).build ();
101
158
}
102
159
}
103
160
104
161
@ Override
105
- public ConfigOrError <RlqsFilterConfig > parseFilterConfigOverride (Message rawProtoMessage ) {
106
- try {
107
- RlqsFilterConfig rlqsFilterConfig =
108
- parseRlqsFilterOverride (unpackAny (rawProtoMessage , RateLimitQuotaOverride .class ));
109
- return ConfigOrError .fromConfig (rlqsFilterConfig );
110
- } catch (InvalidProtocolBufferException e ) {
111
- return ConfigOrError .fromError ("Can't unpack RateLimitQuotaOverride proto: " + e );
112
- } catch (ResourceInvalidException e ) {
113
- return ConfigOrError .fromError (e .getMessage ());
162
+ public void close () {
163
+ // TODO(sergiitk): [DESIGN] besides shutting down everything, should there
164
+ // be per-route interceptor destructors?
165
+ if (!shutdown .compareAndSet (false , true )) {
166
+ throw new ConcurrentModificationException (
167
+ "Unexpected: RlqsFilter#close called multiple times" );
168
+ }
169
+ RlqsCache oldCache = rlqsCache .getAndUpdate (unused -> null );
170
+ if (oldCache != null ) {
171
+ oldCache .shutdown ();
114
172
}
115
173
}
116
174
175
+ // @Override
176
+ public boolean isEnabled () {
177
+ return enabled ;
178
+ }
179
+
117
180
@ Nullable
118
181
@ Override
119
182
public ServerInterceptor buildServerInterceptor (
120
183
FilterConfig config , @ Nullable FilterConfig overrideConfig ) {
121
- throw new UnsupportedOperationException ("ScheduledExecutorService scheduler required" );
122
- }
184
+ // ScheduledExecutorService scheduler
123
185
124
- @ Override
125
- public ServerInterceptor buildServerInterceptor (
126
- FilterConfig config ,
127
- @ Nullable FilterConfig overrideConfig ,
128
- ScheduledExecutorService scheduler ) {
129
186
// Called when we get an xds update - when the LRS or RLS changes.
130
187
RlqsFilterConfig rlqsFilterConfig = (RlqsFilterConfig ) checkNotNull (config , "config" );
131
188
@@ -148,16 +205,6 @@ public ServerInterceptor buildServerInterceptor(
148
205
return generateRlqsInterceptor (rlqsFilterConfig );
149
206
}
150
207
151
- @ Override
152
- public void shutdown () {
153
- // TODO(sergiitk): [DESIGN] besides shutting down everything, should there
154
- // be per-route interceptor destructors?
155
- RlqsCache oldCache = rlqsCache .getAndUpdate (unused -> null );
156
- if (oldCache != null ) {
157
- oldCache .shutdown ();
158
- }
159
- }
160
-
161
208
@ Nullable
162
209
private ServerInterceptor generateRlqsInterceptor (RlqsFilterConfig config ) {
163
210
checkNotNull (config , "config" );
@@ -193,36 +240,6 @@ public <ReqT, RespT> Listener<ReqT> interceptCall(
193
240
};
194
241
}
195
242
196
- @ VisibleForTesting
197
- RlqsFilterConfig parseRlqsFilter (RateLimitQuotaFilterConfig rlqsFilterProto )
198
- throws ResourceInvalidException , InvalidProtocolBufferException {
199
- RlqsFilterConfig .Builder builder = RlqsFilterConfig .builder ();
200
- if (rlqsFilterProto .getDomain ().isEmpty ()) {
201
- throw new ResourceInvalidException ("RateLimitQuotaFilterConfig domain is required" );
202
- }
203
- builder .domain (rlqsFilterProto .getDomain ())
204
- .rlqsService (GrpcService .fromEnvoyProto (rlqsFilterProto .getRlqsServer ()));
205
-
206
- // TODO(sergiitk): [IMPL] Remove
207
- if (dryRun ) {
208
- logger .log (XdsLogLevel .DEBUG , "Dry run: not parsing matchers in the filter filter" );
209
- return builder .build ();
210
- }
211
-
212
- // TODO(sergiitk): [IMPL] actually parse, move to RlqsBucketSettings.fromProto()
213
- RateLimitQuotaBucketSettings fallbackBucketSettingsProto = unpackAny (
214
- rlqsFilterProto .getBucketMatchers ().getOnNoMatch ().getAction ().getTypedConfig (),
215
- RateLimitQuotaBucketSettings .class );
216
- RlqsBucketSettings fallbackBucket = RlqsBucketSettings .create (
217
- ImmutableMap .of ("bucket_id" , headers -> "hello" ),
218
- fallbackBucketSettingsProto .getReportingInterval ());
219
-
220
- // TODO(sergiitk): [IMPL] actually parse, move to Matcher.fromProto()
221
- Matcher <HttpMatchInput , RlqsBucketSettings > bucketMatchers = new RlqsMatcher (fallbackBucket );
222
-
223
- return builder .bucketMatchers (bucketMatchers ).build ();
224
- }
225
-
226
243
static class RlqsMatcher extends Matcher <HttpMatchInput , RlqsBucketSettings > {
227
244
private final RlqsBucketSettings fallbackBucket ;
228
245
0 commit comments