Skip to content

Commit 9e000d3

Browse files
authored
remove duplicated routes due to route with retries (#357)
* remove duplicated routes due to route with retries * Test creating routes for retries * refactor defaultDependencySettings
1 parent f1ae753 commit 9e000d3

File tree

7 files changed

+208
-89
lines changed

7 files changed

+208
-89
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
77

88
### Changed
99
- flaky test fixed
10+
- remove duplicated routes
1011

1112
## [0.19.26]
1213

docs/configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ Where `<selector>` is one of the following:
184184
### Outgoing traffic
185185
Property | Description | Default value
186186
--------------------------------------------------------------------------------------------------------| ----------------------------------------------------------------------------------------------------------------------------- | ---------
187+
**envoy-control.envoy.snapshot.retryPolicy.enabled** | Flag which enables default retries | true
187188
**envoy-control.envoy.snapshot.retryPolicy.numberOfRetries** | Number of retries | 1
188189
**envoy-control.envoy.snapshot.retryPolicy.hostSelectionRetryMaxAttempts** | The maximum number of times host selection will be reattempted before request being routed to last selected host | 3
189190
**envoy-control.envoy.snapshot.retryPolicy.retryHostPredicate** | Specifies a collection of RetryHostPredicates that will be consulted when selecting a host for retries | a list with one entry "envoy.retry_host_predicates.previous_hosts"

envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadata.kt

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import io.envoyproxy.controlplane.server.exception.RequestException
88
import io.grpc.Status
99
import pl.allegro.tech.servicemesh.envoycontrol.groups.ClientWithSelector.Companion.decomposeClient
1010
import pl.allegro.tech.servicemesh.envoycontrol.snapshot.AccessLogFiltersProperties
11+
import pl.allegro.tech.servicemesh.envoycontrol.snapshot.CommonHttpProperties
12+
import pl.allegro.tech.servicemesh.envoycontrol.snapshot.RetryPolicyProperties
1113
import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotProperties
1214
import pl.allegro.tech.servicemesh.envoycontrol.utils.AccessLogFilterParser
1315
import pl.allegro.tech.servicemesh.envoycontrol.utils.ComparisonFilterSettings
@@ -95,30 +97,40 @@ fun Value?.toHeaderFilter(default: String? = null): HeaderFilterSettings? {
9597

9698
private class RawDependency(val service: String?, val domain: String?, val domainPattern: String?, val value: Value)
9799

100+
private fun defaultRetryPolicy(retryPolicy: RetryPolicyProperties) = if (retryPolicy.enabled) {
101+
RetryPolicy(
102+
retryOn = retryPolicy.retryOn,
103+
numberRetries = retryPolicy.numberOfRetries,
104+
retryHostPredicate = retryPolicy.retryHostPredicate,
105+
hostSelectionRetryMaxAttempts = retryPolicy.hostSelectionRetryMaxAttempts,
106+
rateLimitedRetryBackOff = RateLimitedRetryBackOff(
107+
retryPolicy.rateLimitedRetryBackOff.resetHeaders.map { ResetHeader(it.name, it.format) }
108+
),
109+
retryBackOff = RetryBackOff(
110+
Durations.fromMillis(retryPolicy.retryBackOff.baseInterval.toMillis())
111+
),
112+
)
113+
} else {
114+
null
115+
}
116+
117+
private fun defaultTimeoutPolicy(commonHttpProperties: CommonHttpProperties) = Outgoing.TimeoutPolicy(
118+
idleTimeout = Durations.fromMillis(commonHttpProperties.idleTimeout.toMillis()),
119+
connectionIdleTimeout = Durations.fromMillis(commonHttpProperties.connectionIdleTimeout.toMillis()),
120+
requestTimeout = Durations.fromMillis(commonHttpProperties.requestTimeout.toMillis())
121+
)
122+
123+
private fun defaultDependencySettings(properties: SnapshotProperties) = DependencySettings(
124+
handleInternalRedirect = properties.egress.handleInternalRedirect,
125+
timeoutPolicy = defaultTimeoutPolicy(properties.egress.commonHttp),
126+
retryPolicy = defaultRetryPolicy(properties.retryPolicy)
127+
)
128+
98129
fun Value?.toOutgoing(properties: SnapshotProperties): Outgoing {
99130
val allServiceDependenciesIdentifier = properties.outgoingPermissions.allServicesDependencies.identifier
100131
val rawDependencies = this?.field("dependencies")?.list().orEmpty().map(::toRawDependency)
101132
val allServicesDependencies = toAllServiceDependencies(rawDependencies, allServiceDependenciesIdentifier)
102-
val defaultSettingsFromProperties = DependencySettings(
103-
handleInternalRedirect = properties.egress.handleInternalRedirect,
104-
timeoutPolicy = Outgoing.TimeoutPolicy(
105-
idleTimeout = Durations.fromMillis(properties.egress.commonHttp.idleTimeout.toMillis()),
106-
connectionIdleTimeout = Durations.fromMillis(properties.egress.commonHttp.connectionIdleTimeout.toMillis()),
107-
requestTimeout = Durations.fromMillis(properties.egress.commonHttp.requestTimeout.toMillis())
108-
),
109-
retryPolicy = RetryPolicy(
110-
retryOn = properties.retryPolicy.retryOn,
111-
numberRetries = properties.retryPolicy.numberOfRetries,
112-
retryHostPredicate = properties.retryPolicy.retryHostPredicate,
113-
hostSelectionRetryMaxAttempts = properties.retryPolicy.hostSelectionRetryMaxAttempts,
114-
rateLimitedRetryBackOff = RateLimitedRetryBackOff(
115-
properties.retryPolicy.rateLimitedRetryBackOff.resetHeaders.map { ResetHeader(it.name, it.format) }
116-
),
117-
retryBackOff = RetryBackOff(
118-
Durations.fromMillis(properties.retryPolicy.retryBackOff.baseInterval.toMillis())
119-
),
120-
)
121-
)
133+
val defaultSettingsFromProperties = defaultDependencySettings(properties)
122134
val allServicesDefaultSettings = allServicesDependencies?.value.toSettings(defaultSettingsFromProperties)
123135
val services = rawDependencies.filter { it.service != null && it.service != allServiceDependenciesIdentifier }
124136
.map {
@@ -239,27 +251,27 @@ private fun Value?.toSettings(defaultSettings: DependencySettings): DependencySe
239251
}
240252
}
241253

242-
private fun mapProtoToRetryPolicy(value: Value, defaultRetryPolicy: RetryPolicy): RetryPolicy {
254+
private fun mapProtoToRetryPolicy(value: Value, defaultRetryPolicy: RetryPolicy?): RetryPolicy {
243255
return RetryPolicy(
244-
retryOn = value.field("retryOn")?.listValue?.valuesList?.map { it.stringValue } ?: defaultRetryPolicy.retryOn,
256+
retryOn = value.field("retryOn")?.listValue?.valuesList?.map { it.stringValue } ?: defaultRetryPolicy?.retryOn,
245257
hostSelectionRetryMaxAttempts = value.field("hostSelectionRetryMaxAttempts")?.numberValue?.toLong()
246-
?: defaultRetryPolicy.hostSelectionRetryMaxAttempts,
247-
numberRetries = value.field("numberRetries")?.numberValue?.toInt() ?: defaultRetryPolicy.numberRetries,
258+
?: defaultRetryPolicy?.hostSelectionRetryMaxAttempts,
259+
numberRetries = value.field("numberRetries")?.numberValue?.toInt() ?: defaultRetryPolicy?.numberRetries,
248260
retryHostPredicate = value.field("retryHostPredicate")?.listValue?.valuesList?.map {
249261
RetryHostPredicate(it.field("name")!!.stringValue)
250-
}?.toList() ?: defaultRetryPolicy.retryHostPredicate,
262+
}?.toList() ?: defaultRetryPolicy?.retryHostPredicate,
251263
perTryTimeoutMs = value.field("perTryTimeoutMs")?.numberValue?.toLong(),
252264
retryBackOff = value.field("retryBackOff")?.structValue?.let {
253265
RetryBackOff(
254266
baseInterval = it.fieldsMap["baseInterval"]?.toDuration(),
255267
maxInterval = it.fieldsMap["maxInterval"]?.toDuration()
256268
)
257-
} ?: defaultRetryPolicy.retryBackOff,
269+
} ?: defaultRetryPolicy?.retryBackOff,
258270
rateLimitedRetryBackOff = value.field("rateLimitedRetryBackOff")?.structValue?.let {
259271
RateLimitedRetryBackOff(
260272
it.fieldsMap["resetHeaders"]?.listValue?.valuesList?.mapNotNull(::mapProtoToResetHeader)
261273
)
262-
} ?: defaultRetryPolicy.rateLimitedRetryBackOff,
274+
} ?: defaultRetryPolicy?.rateLimitedRetryBackOff,
263275
retryableStatusCodes = value.field("retryableStatusCodes")?.listValue?.valuesList?.map {
264276
it.numberValue.toInt()
265277
},
@@ -573,11 +585,11 @@ data class DependencySettings(
573585
val handleInternalRedirect: Boolean = false,
574586
val timeoutPolicy: Outgoing.TimeoutPolicy = Outgoing.TimeoutPolicy(),
575587
val rewriteHostHeader: Boolean = false,
576-
val retryPolicy: RetryPolicy = RetryPolicy()
588+
val retryPolicy: RetryPolicy? = RetryPolicy()
577589
)
578590

579591
data class RetryPolicy(
580-
val retryOn: List<String> = emptyList(),
592+
val retryOn: List<String>? = emptyList(),
581593
val hostSelectionRetryMaxAttempts: Long? = null,
582594
val numberRetries: Int? = null,
583595
val retryHostPredicate: List<RetryHostPredicate>? = null,

envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotProperties.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ data class RateLimitProperties(
372372
)
373373

374374
data class RetryPolicyProperties(
375+
var enabled: Boolean = true,
375376
var retryOn: List<String> = emptyList(),
376377
var numberOfRetries: Int = 1,
377378
var hostSelectionRetryMaxAttempts: Long = 3,

envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/routes/EnvoyEgressRoutesFactory.kt

Lines changed: 52 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ class EnvoyEgressRoutesFactory(
7272
.setValue("%UPSTREAM_REMOTE_ADDRESS%").build()
7373
)
7474

75+
private val defaultRouteMatch = RouteMatch
76+
.newBuilder()
77+
.setPrefix("/")
78+
.build()
79+
7580
/**
7681
* @see TestResources.createRoute
7782
*/
@@ -84,12 +89,7 @@ class EnvoyEgressRoutesFactory(
8489
val virtualHosts = routes
8590
.filter { it.routeDomains.isNotEmpty() }
8691
.map { routeSpecification ->
87-
addMultipleRoutes(
88-
VirtualHost.newBuilder()
89-
.setName(routeSpecification.clusterName)
90-
.addAllDomains(routeSpecification.routeDomains),
91-
routeSpecification
92-
).build()
92+
buildEgressRoute(routeSpecification)
9393
}
9494

9595
var routeConfiguration = RouteConfiguration.newBuilder()
@@ -120,37 +120,55 @@ class EnvoyEgressRoutesFactory(
120120
return routeConfiguration.build()
121121
}
122122

123-
private fun addMultipleRoutes(
124-
addAllDomains: VirtualHost.Builder,
125-
routeSpecification: RouteSpecification
126-
): VirtualHost.Builder {
127-
routeSpecification.settings.retryPolicy.let {
128-
buildRouteForRetryPolicy(addAllDomains, routeSpecification)
123+
private fun buildEgressRoute(routeSpecification: RouteSpecification): VirtualHost {
124+
val virtualHost = VirtualHost.newBuilder()
125+
.setName(routeSpecification.clusterName)
126+
.addAllDomains(routeSpecification.routeDomains)
127+
val retryPolicy = routeSpecification.settings.retryPolicy
128+
if (retryPolicy != null) {
129+
buildEgressRouteWithRetryPolicy(virtualHost, retryPolicy, routeSpecification)
130+
} else {
131+
virtualHost.addRoutes(
132+
Route.newBuilder()
133+
.setMatch(defaultRouteMatch)
134+
.setRoute(createRouteAction(routeSpecification, shouldAddRetryPolicy = false))
135+
.build()
136+
)
129137
}
130-
buildDefaultRoute(addAllDomains, routeSpecification)
131-
return addAllDomains
138+
return virtualHost.build()
132139
}
133140

134-
private fun buildRouteForRetryPolicy(
135-
addAllDomains: VirtualHost.Builder,
141+
private fun buildEgressRouteWithRetryPolicy(
142+
virtualHost: VirtualHost.Builder,
143+
retryPolicy: EnvoyControlRetryPolicy,
136144
routeSpecification: RouteSpecification
137-
): VirtualHost.Builder? {
138-
val regexAsAString = routeSpecification.settings.retryPolicy.methods?.joinToString(separator = "|")
139-
val routeMatchBuilder = RouteMatch
140-
.newBuilder()
141-
.setPrefix("/")
142-
.also { routeMatcher ->
143-
regexAsAString?.let {
144-
routeMatcher.addHeaders(buildMethodHeaderMatcher(it))
145-
}
146-
}
147-
148-
return addAllDomains.addRoutes(
149-
Route.newBuilder()
150-
.setMatch(routeMatchBuilder.build())
151-
.setRoute(createRouteAction(routeSpecification, shouldAddRetryPolicy = true))
145+
): VirtualHost.Builder {
146+
return if (retryPolicy.methods != null) {
147+
val regexAsAString = retryPolicy.methods.joinToString(separator = "|")
148+
val retryRouteMatch = RouteMatch
149+
.newBuilder()
150+
.setPrefix("/")
151+
.addHeaders(buildMethodHeaderMatcher(regexAsAString))
152152
.build()
153-
)
153+
virtualHost.addRoutes(
154+
Route.newBuilder()
155+
.setMatch(retryRouteMatch)
156+
.setRoute(createRouteAction(routeSpecification, shouldAddRetryPolicy = true))
157+
.build()
158+
).addRoutes(
159+
Route.newBuilder()
160+
.setMatch(defaultRouteMatch)
161+
.setRoute(createRouteAction(routeSpecification, shouldAddRetryPolicy = false))
162+
.build()
163+
)
164+
} else {
165+
virtualHost.addRoutes(
166+
Route.newBuilder()
167+
.setMatch(defaultRouteMatch)
168+
.setRoute(createRouteAction(routeSpecification, shouldAddRetryPolicy = true))
169+
.build()
170+
)
171+
}
154172
}
155173

156174
private fun buildMethodHeaderMatcher(regexAsAString: String) = HeaderMatcher.newBuilder()
@@ -162,23 +180,6 @@ class EnvoyEgressRoutesFactory(
162180
.build()
163181
)
164182

165-
private fun buildDefaultRoute(
166-
addAllDomains: VirtualHost.Builder,
167-
routeSpecification: RouteSpecification
168-
) {
169-
addAllDomains.addRoutes(
170-
Route.newBuilder()
171-
.setMatch(
172-
RouteMatch.newBuilder()
173-
.setPrefix("/")
174-
.build()
175-
)
176-
.setRoute(
177-
createRouteAction(routeSpecification)
178-
).build()
179-
)
180-
}
181-
182183
/**
183184
* @see TestResources.createRoute
184185
*/
@@ -194,11 +195,7 @@ class EnvoyEgressRoutesFactory(
194195
.addAllDomains(routeSpecification.routeDomains)
195196
.addRoutes(
196197
Route.newBuilder()
197-
.setMatch(
198-
RouteMatch.newBuilder()
199-
.setPrefix("/")
200-
.build()
201-
)
198+
.setMatch(defaultRouteMatch)
202199
.setRoute(
203200
createRouteAction(routeSpecification)
204201
).build()
@@ -252,7 +249,7 @@ class RequestPolicyMapper private constructor() {
252249
return retryPolicy?.let { policy ->
253250
val retryPolicyBuilder = RetryPolicy.newBuilder()
254251

255-
policy.retryOn.let { retryPolicyBuilder.setRetryOn(it.joinToString { joined -> joined }) }
252+
policy.retryOn?.let { retryPolicyBuilder.setRetryOn(it.joinToString { joined -> joined }) }
256253
policy.hostSelectionRetryMaxAttempts?.let {
257254
retryPolicyBuilder.setHostSelectionRetryMaxAttempts(it)
258255
}

envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/RoutesAssertions.kt

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ fun RouteConfiguration.hasSingleVirtualHostThat(condition: VirtualHost.() -> Uni
2222
return this
2323
}
2424

25+
fun RouteConfiguration.hasVirtualHostThat(name: String, condition: VirtualHost.() -> Unit): RouteConfiguration {
26+
condition(this.virtualHostsList.find { it.name == name }!!)
27+
return this
28+
}
29+
2530
fun RouteConfiguration.hasRequestHeaderToAdd(key: String, value: String): RouteConfiguration {
2631
assertThat(this.requestHeadersToAddList).anySatisfy {
2732
assertThat(it.header.key).isEqualTo(key)
@@ -136,15 +141,22 @@ fun Route.matchingOnPath(path: String): Route {
136141
return this
137142
}
138143

139-
fun Route.matchingOnHeader(name: String, value: String): Route {
144+
fun Route.matchingOnHeader(name: String, value: String, isRegex: Boolean = false): Route {
145+
val matcher = RegexMatcher.newBuilder().setRegex(value)
146+
.setGoogleRe2(RegexMatcher.GoogleRE2.getDefaultInstance())
147+
.build()
140148
assertThat(this.match.headersList).anyMatch {
141-
it.name == name && it.exactMatch == value
149+
if (isRegex) {
150+
it.name == name && it.safeRegexMatch == matcher
151+
} else {
152+
it.name == name && it.exactMatch == value
153+
}
142154
}
143155
return this
144156
}
145157

146-
fun Route.matchingOnMethod(method: String): Route {
147-
return this.matchingOnHeader(":method", method)
158+
fun Route.matchingOnMethod(method: String, isRegex: Boolean = false): Route {
159+
return this.matchingOnHeader(":method", method, isRegex)
148160
}
149161

150162
fun Route.matchingOnAnyMethod(): Route {
@@ -207,6 +219,10 @@ fun Route.hasNoRetryPolicy() {
207219
assertThat(this.route.retryPolicy).isEqualTo(RetryPolicy.newBuilder().build())
208220
}
209221

222+
fun Route.hasRetryPolicy() {
223+
assertThat(this.route.hasRetryPolicy()).isTrue()
224+
}
225+
210226
fun Route.ingressRoute() {
211227
this.matchingOnPrefix("/")
212228
.publicAccess()

0 commit comments

Comments
 (0)