From 53efcdd33fd253063b7ff20b7886273d06537dad Mon Sep 17 00:00:00 2001 From: Kamil Smigielski Date: Fri, 2 Dec 2022 22:53:07 +0100 Subject: [PATCH 01/15] tag dependency PoC --- .../snapshot/EnvoySnapshotFactoryTest.kt | 4 ++ .../snapshot/resource/ResourceUtils.kt | 2 + .../clusters/EnvoyClusterFactoryTest.kt | 2 + .../listeners/EnvoyListenersFactoryTest.kt | 4 ++ tools/docker-compose.yaml | 5 +- tools/envoy-control/Dockerfile | 18 ++---- tools/envoy-control/application-docker.yaml | 9 +++ tools/envoy/envoy-template.yaml | 2 +- tools/service/register_and_run.sh | 60 +++++++++++++++---- 9 files changed, 76 insertions(+), 30 deletions(-) create mode 100644 envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt create mode 100644 envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/ResourceUtils.kt create mode 100644 envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt create mode 100644 envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt create mode 100644 tools/envoy-control/application-docker.yaml diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt new file mode 100644 index 000000000..a185b02c1 --- /dev/null +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt @@ -0,0 +1,4 @@ +package pl.allegro.tech.servicemesh.envoycontrol.snapshot + +class EnvoySnapshotFactory { +} diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/ResourceUtils.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/ResourceUtils.kt new file mode 100644 index 000000000..3529d3845 --- /dev/null +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/ResourceUtils.kt @@ -0,0 +1,2 @@ +package pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource + diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt new file mode 100644 index 000000000..479f1ef8d --- /dev/null +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt @@ -0,0 +1,2 @@ +package pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.clusters;public class EnvoyClusterFactoryTest { +} diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt new file mode 100644 index 000000000..e635b32b9 --- /dev/null +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt @@ -0,0 +1,4 @@ +package pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.listeners + +class EnvoyListenersFactoryTest { +} diff --git a/tools/docker-compose.yaml b/tools/docker-compose.yaml index 63606a60e..0d5b71adb 100644 --- a/tools/docker-compose.yaml +++ b/tools/docker-compose.yaml @@ -3,7 +3,7 @@ version: '3' services: consul: container_name: consul - image: consul:1.5.3 + image: consul:1.10.12 ports: - "18500:8500" - "18300:8300" @@ -36,9 +36,6 @@ services: ports: - "8080:8080" - "50000:50000" - # here you can define path to your own config - volumes: - - "../envoy-control-runner/src/main/resources/application-docker.yaml:/var/tmp/config/application.yaml" depends_on: - consul environment: diff --git a/tools/envoy-control/Dockerfile b/tools/envoy-control/Dockerfile index 790daa9f0..5060cd793 100644 --- a/tools/envoy-control/Dockerfile +++ b/tools/envoy-control/Dockerfile @@ -1,23 +1,13 @@ -FROM gradle:6.6.1-jdk11 AS builder -COPY --chown=gradle:gradle settings.gradle build.gradle gradle.properties /home/gradle/src/ -COPY --chown=gradle:gradle envoy-control-core/ /home/gradle/src/envoy-control-core/ -COPY --chown=gradle:gradle envoy-control-runner/ /home/gradle/src/envoy-control-runner/ -COPY --chown=gradle:gradle envoy-control-services/ /home/gradle/src/envoy-control-services/ -COPY --chown=gradle:gradle envoy-control-source-consul/ /home/gradle/src/envoy-control-source-consul/ - -WORKDIR /home/gradle/src -RUN gradle :envoy-control-runner:assemble --parallel --no-daemon - FROM adoptopenjdk/openjdk11:alpine-jre RUN mkdir /tmp/envoy-control-dist /tmp/envoy-control /bin/envoy-control /etc/envoy-control /var/tmp/config -COPY --from=builder /home/gradle/src/envoy-control-runner/build/distributions/ /tmp/envoy-control-dist +COPY tools/envoy-control/run.sh /usr/local/bin/run.sh +VOLUME /var/tmp/config +COPY ../../envoy-control-runner/build/distributions /tmp/envoy-control-dist COPY ./envoy-control-runner/src/main/resources/application-docker.yaml /etc/envoy-control/application.yaml -RUN tar -xf /tmp/envoy-control-dist/envoy-control-runner-0.1.0*.tar -C /tmp/envoy-control \ +RUN tar -xf /tmp/envoy-control-dist/envoy-control-runner-*.tar -C /tmp/envoy-control \ && mv /tmp/envoy-control/envoy-control-runner*/ /bin/envoy-control/envoy-control-runner -COPY tools/envoy-control/run.sh /usr/local/bin/run.sh -VOLUME /var/tmp/config WORKDIR /usr/local/bin/ # APP_PORT: 8080 diff --git a/tools/envoy-control/application-docker.yaml b/tools/envoy-control/application-docker.yaml new file mode 100644 index 000000000..9a720d978 --- /dev/null +++ b/tools/envoy-control/application-docker.yaml @@ -0,0 +1,9 @@ +envoy-control: + source: + consul: + host: consul + port: 8500 + +chaos: + username: "user" + password: "pass" diff --git a/tools/envoy/envoy-template.yaml b/tools/envoy/envoy-template.yaml index 232a2ccba..5bd0b057a 100644 --- a/tools/envoy/envoy-template.yaml +++ b/tools/envoy/envoy-template.yaml @@ -10,7 +10,7 @@ node: proxy_settings: outgoing: dependencies: - - service: "*" + - tag: hermes-consumers locality: zone: default-zone diff --git a/tools/service/register_and_run.sh b/tools/service/register_and_run.sh index 0602041d9..d04b8a511 100644 --- a/tools/service/register_and_run.sh +++ b/tools/service/register_and_run.sh @@ -4,20 +4,33 @@ set -o pipefail set -o errexit port=80 -service_name=http-echo -instance_id="${service_name}-1" - -echo "Registering instance of ${service_name} in consul" -echo "=============================" -echo -echo +service_name1=http-echo1 +service_name2=http-echo2 +service_name3=http-echo3 ip="$(hostname -i)" -body=' +body1=' +{ + "ID": "'${service_name1}'-1", + "Name": "'${service_name1}'", + "Tags": [ + "primary", + "hermes-consumers" + ], + "Address": "'${ip}'", + "Port": '${port}', + "Check": { + "DeregisterCriticalServiceAfter": "90m", + "http": "http://'${ip}:${port}'", + "Interval": "10s" + } +} +' +body2=' { - "ID": "'${instance_id}'", - "Name": "'${service_name}'", + "ID": "'${service_name2}'-1", + "Name": "'${service_name2}'", "Tags": [ "primary" ], @@ -30,7 +43,32 @@ body=' } } ' -curl -X PUT --fail --data "${body}" -s consul:8500/v1/agent/service/register +body3=' +{ + "ID": "'${service_name3}'-1", + "Name": "'${service_name3}'", + "Tags": [ + "primary", + "hermes-consumers" + ], + "Address": "'${ip}'", + "Port": '${port}', + "Check": { + "DeregisterCriticalServiceAfter": "90m", + "http": "http://'${ip}:${port}'", + "Interval": "10s" + } +} +' + +sleep 15 + +echo "Registering instance of ${service_name1} in consul" +curl -X PUT --data "${body1}" consul:8500/v1/agent/service/register +echo "Registering instance of ${service_name2} in consul" +curl -X PUT --data "${body2}" consul:8500/v1/agent/service/register +echo "Registering instance of ${service_name3} in consul" +curl -X PUT --data "${body3}" consul:8500/v1/agent/service/register cd /app node ./index.js From 8840961a20079e3a2c1060d0a2ef7dd536fee31a Mon Sep 17 00:00:00 2001 From: Kamil Smigielski Date: Fri, 2 Dec 2022 22:56:31 +0100 Subject: [PATCH 02/15] tag dependency PoC --- .../servicemesh/envoycontrol/ControlPlane.kt | 4 +- .../envoycontrol/groups/MetadataNodeGroup.kt | 3 + .../envoycontrol/groups/NodeMetadata.kt | 51 +- .../groups/NodeMetadataValidator.kt | 13 +- .../snapshot/EnvoySnapshotFactory.kt | 164 ++- .../envoycontrol/snapshot/GlobalSnapshot.kt | 9 +- .../snapshot/SnapshotProperties.kt | 1 + .../resource/clusters/EnvoyClustersFactory.kt | 41 +- .../listeners/EnvoyListenersFactory.kt | 25 +- .../listeners/filters/AccessLogFilter.kt | 4 +- .../filters/HttpConnectionManagerFactory.kt | 2 +- .../routes/EnvoyEgressRoutesFactory.kt | 28 +- .../routes/EnvoyIngressRoutesFactory.kt | 4 +- .../envoycontrol/EnvoySnapshotFactoryTest.kt | 15 +- .../envoycontrol/groups/NodeMetadataTest.kt | 1207 ++++++++++------- .../groups/NodeMetadataValidatorTest.kt | 179 +-- .../envoycontrol/groups/TestNodeFactory.kt | 46 +- .../snapshot/EnvoySnapshotFactoryTest.kt | 81 +- .../snapshot/SnapshotUpdaterTest.kt | 1 + .../snapshot/resource/ResourceUtils.kt | 59 + .../clusters/EnvoyClusterFactoryTest.kt | 295 +++- .../listeners/EnvoyListenersFactoryTest.kt | 96 ++ .../filters/rbac/RBACFilterFactoryJwtTest.kt | 3 +- .../filters/rbac/RBACFilterFactoryTest.kt | 6 +- .../main/resources/application-docker.yaml | 8 +- .../src/main/resources/application.yaml | 2 + .../envoycontrol/OutgoingPermissionsTest.kt | 6 + .../src/main/resources/envoy/config_ads.yaml | 1 + .../src/main/resources/envoy/config_xds.yaml | 1 + 29 files changed, 1624 insertions(+), 731 deletions(-) diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/ControlPlane.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/ControlPlane.kt index ac431e297..10ce38b28 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/ControlPlane.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/ControlPlane.kt @@ -26,6 +26,7 @@ import pl.allegro.tech.servicemesh.envoycontrol.server.callbacks.MetricsDiscover import pl.allegro.tech.servicemesh.envoycontrol.services.MultiClusterState import pl.allegro.tech.servicemesh.envoycontrol.snapshot.EnvoySnapshotFactory import pl.allegro.tech.servicemesh.envoycontrol.snapshot.NoopSnapshotChangeAuditor +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.RouteSpecificationFactory import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotChangeAuditor import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotUpdater import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotsVersions @@ -167,7 +168,7 @@ class ControlPlane private constructor( val snapshotProperties = properties.envoy.snapshot val envoySnapshotFactory = EnvoySnapshotFactory( ingressRoutesFactory = EnvoyIngressRoutesFactory(snapshotProperties, envoyHttpFilters), - egressRoutesFactory = EnvoyEgressRoutesFactory(snapshotProperties), + egressRoutesFactory = EnvoyEgressRoutesFactory(snapshotProperties.egress, snapshotProperties.incomingPermissions), clustersFactory = EnvoyClustersFactory(snapshotProperties), endpointsFactory = EnvoyEndpointsFactory( snapshotProperties, ServiceTagMetadataGenerator(snapshotProperties.routing.serviceTags) @@ -176,6 +177,7 @@ class ControlPlane private constructor( snapshotProperties, envoyHttpFilters ), + routeSpecificationFactory = RouteSpecificationFactory(snapshotProperties), // Remember when LDS change we have to send RDS again snapshotsVersions = snapshotsVersions, properties = snapshotProperties, diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/MetadataNodeGroup.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/MetadataNodeGroup.kt index f25228335..8a8169da7 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/MetadataNodeGroup.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/MetadataNodeGroup.kt @@ -135,6 +135,7 @@ class MetadataNodeGroup( val proxySettings = proxySettings(nodeMetadata) val listenersConfig = createListenersConfig(node.id, node.metadata) + println("ksksks hasAllServicesDependencies(nodeMetadata) ${hasAllServicesDependencies(nodeMetadata)}") return when { hasAllServicesDependencies(nodeMetadata) -> AllServicesGroup( @@ -156,6 +157,8 @@ class MetadataNodeGroup( } private fun hasAllServicesDependencies(metadata: NodeMetadata): Boolean { + println("ksksks !properties.outgoingPermissions.enabled ${!properties.outgoingPermissions.enabled}") + println("ksksks metadata.proxySettings.outgoing.allServicesDependencies ${metadata.proxySettings.outgoing.allServicesDependencies}") return !properties.outgoingPermissions.enabled || metadata.proxySettings.outgoing.allServicesDependencies } diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadata.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadata.kt index 07b4ebc11..eb6c465db 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadata.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadata.kt @@ -93,7 +93,7 @@ fun Value?.toHeaderFilter(default: String? = null): HeaderFilterSettings? { } } -private class RawDependency(val service: String?, val domain: String?, val domainPattern: String?, val value: Value) +private class RawDependency(val service: String?, val domain: String?, val domainPattern: String?, val tag: String?, val value: Value) fun Value?.toOutgoing(properties: SnapshotProperties): Outgoing { val allServiceDependenciesIdentifier = properties.outgoingPermissions.allServicesDependencies.identifier @@ -133,10 +133,19 @@ fun Value?.toOutgoing(properties: SnapshotProperties): Outgoing { val domainPatterns = rawDependencies.filter { it.domainPattern != null } .onEach { validateDomainPatternFormat(it) } .map { DomainPatternDependency(it.domainPattern.orEmpty(), it.value.toSettings(defaultSettingsFromProperties)) } + + val tags = rawDependencies.filter { it.tag != null } + .map { + TagDependency( + tag = it.tag!!, + settings = it.value.toSettings(allServicesDefaultSettings) + ) + } return Outgoing( serviceDependencies = services, domainDependencies = domains, domainPatternDependencies = domainPatterns, + tagDependencies = tags, defaultServiceSettings = allServicesDefaultSettings, allServicesDependencies = allServicesDependencies != null ) @@ -147,6 +156,7 @@ private fun toRawDependency(it: Value): RawDependency { val service = it.field("service")?.stringValue val domain = it.field("domain")?.stringValue val domainPattern = it.field("domainPattern")?.stringValue + val tag = it.field("tag")?.stringValue var count = 0 if (!service.isNullOrBlank()) { count += 1 @@ -157,6 +167,9 @@ private fun toRawDependency(it: Value): RawDependency { if (!domainPattern.isNullOrBlank()) { count += 1 } + if (!tag.isNullOrBlank()) { + count += 1 + } if (count != 1) { throw NodeMetadataValidationException( "Define one of: 'service', 'domain' or 'domainPattern' as an outgoing dependency" @@ -166,6 +179,7 @@ private fun toRawDependency(it: Value): RawDependency { service = service, domain = domain, domainPattern = domainPattern, + tag = tag, value = it ) } @@ -487,27 +501,30 @@ data class Outgoing( private val serviceDependencies: List = emptyList(), private val domainDependencies: List = emptyList(), private val domainPatternDependencies: List = emptyList(), + private val tagDependencies: List = emptyList(), val allServicesDependencies: Boolean = false, val defaultServiceSettings: DependencySettings = DependencySettings() ) { // not declared in primary constructor to exclude from equals(), copy(), etc. - private val deduplicatedDomainDependencies: List = domainDependencies - .map { it.domain to it } - .toMap().values.toList() + private val deduplicatedDomainDependencies: List = + domainDependencies.associateBy { it.domain }.values.toList() + + private val deduplicatedServiceDependencies: List = + serviceDependencies.associateBy { it.service }.values.toList() - private val deduplicatedServiceDependencies: List = serviceDependencies - .map { it.service to it } - .toMap().values.toList() + private val deduplicatedDomainPatternDependencies: List = + domainPatternDependencies.associateBy { it.domainPattern }.values.toList() - private val deduplicatedDomainPatternDependencies: List = domainPatternDependencies - .map { it.domainPattern to it } - .toMap().values.toList() + private val deduplicatedTagDependency: List = + tagDependencies.associateBy { it.tag }.values.toList() fun getDomainDependencies(): List = deduplicatedDomainDependencies fun getServiceDependencies(): List = deduplicatedServiceDependencies fun getDomainPatternDependencies(): List = deduplicatedDomainPatternDependencies + fun getTagDependencies(): List = deduplicatedTagDependency + data class TimeoutPolicy( val idleTimeout: Duration? = null, val connectionIdleTimeout: Duration? = null, @@ -569,6 +586,20 @@ data class DomainPatternDependency( override fun useSsl() = DEFAULT_HTTPS_POLICY } +data class TagDependency( + val tag: String, + val settings: DependencySettings = DependencySettings() +) : Dependency { + companion object { + private const val DEFAULT_HTTP_PORT = 80 + private const val DEFAULT_HTTPS_POLICY = false + } + + override fun getPort() = DEFAULT_HTTP_PORT + + override fun useSsl() = DEFAULT_HTTPS_POLICY +} + data class DependencySettings( val handleInternalRedirect: Boolean = false, val timeoutPolicy: Outgoing.TimeoutPolicy = Outgoing.TimeoutPolicy(), diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataValidator.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataValidator.kt index feb5e20c2..e70019d63 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataValidator.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataValidator.kt @@ -18,6 +18,10 @@ class AllDependenciesValidationException(serviceName: String?) : NodeMetadataVal "Blocked service $serviceName from using all dependencies. Only defined services can use all dependencies" ) +class TagDependencyValidationException(serviceName: String?, tags: List) : NodeMetadataValidationException( + "Blocked service $serviceName from using tag dependencies $tags. Only allowed tags are supported." +) + class WildcardPrincipalValidationException(serviceName: String?) : NodeMetadataValidationException( "Blocked service $serviceName from allowing everyone in incoming permissions. " + "Only defined services can use that." @@ -99,10 +103,15 @@ class NodeMetadataValidator( if (!properties.outgoingPermissions.enabled) { return } - validateEndpointPermissionsMethods(metadata) if (hasAllServicesDependencies(metadata) && !isAllowedToHaveAllServiceDependencies(metadata)) { throw AllDependenciesValidationException(metadata.serviceName) } + if (properties.outgoingPermissions.tagPrefix.isNotBlank()) { + val unsupportedTags = metadata.proxySettings.outgoing.getTagDependencies() + .filter { !it.tag.startsWith(properties.outgoingPermissions.tagPrefix) } + .map { it.tag } + throw TagDependencyValidationException(metadata.serviceName, unsupportedTags) + } } private fun validateIncomingEndpoints(metadata: NodeMetadata) { @@ -110,6 +119,8 @@ class NodeMetadataValidator( return } + validateEndpointPermissionsMethods(metadata) + metadata.proxySettings.incoming.endpoints.forEach { incomingEndpoint -> val clients = incomingEndpoint.clients.map { it.name } diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt index 1357b6eac..0e1eca658 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt @@ -30,13 +30,25 @@ class EnvoySnapshotFactory( private val clustersFactory: EnvoyClustersFactory, private val endpointsFactory: EnvoyEndpointsFactory, private val listenersFactory: EnvoyListenersFactory, + private val routeSpecificationFactory: RouteSpecificationFactory, private val snapshotsVersions: SnapshotsVersions, private val properties: SnapshotProperties, - private val meterRegistry: MeterRegistry + private val meterRegistry: MeterRegistry, ) { companion object { const val DEFAULT_HTTP_PORT = 80 + + internal fun tagExtractor(tagPrefix: String, servicesStates: MultiClusterState): Map> + = servicesStates.flatMap { it.servicesState.serviceNameToInstances.asIterable() } + .fold(emptyMap()) { + acc, entry -> + val value = acc.getOrDefault(entry.key, emptySet()) + val newValue = entry.value.instances + .flatMap { it.tags } + .filter { it.startsWith(tagPrefix) } + acc.plus(entry.key to (value + newValue)) + } } fun newSnapshot( @@ -62,7 +74,8 @@ class EnvoySnapshotFactory( clusters = clusters, securedClusters = securedClusters, endpoints = endpoints, - properties = properties.outgoingPermissions + properties = properties.outgoingPermissions, + tags = tagExtractor(properties.outgoingPermissions.tagPrefix, servicesStates) ) sample.stop(meterRegistry.timer("snapshot-factory.new-snapshot.time")) @@ -155,64 +168,6 @@ class EnvoySnapshotFactory( return newSnapshotForGroup } - private fun getDomainRouteSpecifications(group: Group): Map> { - return group.proxySettings.outgoing.getDomainDependencies().groupBy( - { DomainRoutesGrouper(it.getPort(), it.useSsl()) }, - { - RouteSpecification( - clusterName = it.getClusterName(), - routeDomains = listOf(it.getRouteDomain()), - settings = it.settings - ) - } - ) - } - - private fun getDomainPatternRouteSpecifications(group: Group): RouteSpecification { - return RouteSpecification( - clusterName = properties.dynamicForwardProxy.clusterName, - routeDomains = group.proxySettings.outgoing.getDomainPatternDependencies().map { it.domainPattern }, - settings = group.proxySettings.outgoing.defaultServiceSettings - ) - } - - private fun getServiceRouteSpecifications( - group: Group, - globalSnapshot: GlobalSnapshot - ): Collection { - val definedServicesRoutes = group.proxySettings.outgoing.getServiceDependencies().map { - RouteSpecification( - clusterName = it.service, - routeDomains = listOf(it.service) + getServiceWithCustomDomain(it.service), - settings = it.settings - ) - } - return when (group) { - is ServicesGroup -> { - definedServicesRoutes - } - is AllServicesGroup -> { - val servicesNames = group.proxySettings.outgoing.getServiceDependencies().map { it.service }.toSet() - val allServicesRoutes = globalSnapshot.allServicesNames.subtract(servicesNames).map { - RouteSpecification( - clusterName = it, - routeDomains = listOf(it) + getServiceWithCustomDomain(it), - settings = group.proxySettings.outgoing.defaultServiceSettings - ) - } - allServicesRoutes + definedServicesRoutes - } - } - } - - private fun getServiceWithCustomDomain(it: String): List { - return if (properties.egress.domains.isNotEmpty()) { - properties.egress.domains.map { domain -> "$it$domain" } - } else { - emptyList() - } - } - private fun getServicesEndpointsForGroup( rateLimitEndpoints: List, globalSnapshot: GlobalSnapshot, @@ -232,10 +187,11 @@ class EnvoySnapshotFactory( ): Snapshot { // TODO(dj): This is where serious refactoring needs to be done - val egressDomainRouteSpecifications = getDomainRouteSpecifications(group) - val egressServiceRouteSpecification = getServiceRouteSpecifications(group, globalSnapshot) + val egressDomainRouteSpecifications = routeSpecificationFactory.domainRouteSpecifications(group) + val egressServiceRouteSpecification = routeSpecificationFactory.serviceRouteSpecifications(group, globalSnapshot) val egressRouteSpecification = egressServiceRouteSpecification + - egressDomainRouteSpecifications.values.flatten().toSet() + getDomainPatternRouteSpecifications(group) + egressDomainRouteSpecifications.values.flatten().toSet() + + routeSpecificationFactory.domainPatternRouteSpecifications(group) val clusters: List = clustersFactory.getClustersForGroup(group, globalSnapshot) @@ -358,6 +314,88 @@ data class ClusterConfiguration( val http2Enabled: Boolean ) +class RouteSpecificationFactory( + private val properties: SnapshotProperties +) { + fun serviceRouteSpecifications( + group: Group, + globalSnapshot: GlobalSnapshot + ): Collection { + val definedServicesRoutes = group.proxySettings.outgoing.getServiceDependencies().map { + RouteSpecification( + clusterName = it.service, + routeDomains = listOf(it.service) + getServiceWithCustomDomain(it.service), + settings = it.settings + ) + } + val servicesNames = group.proxySettings.outgoing.getServiceDependencies().map { it.service }.toSet() + val definedTagsRoutes = group.proxySettings.outgoing.getTagDependencies().flatMap { tagDependency -> + globalSnapshot.tags + .filterKeys { !servicesNames.contains(it) } + .filterValues { it.contains(tagDependency.tag) } + .map { RouteSpecification( + clusterName = it.key, + routeDomains = listOf(it.key) + getServiceWithCustomDomain(it.key), + settings = tagDependency.settings + ) } + }.fold(emptyMap()) { + acc, routeSpecification -> + if (acc.containsKey(routeSpecification.clusterName)) { + acc + } else { + acc.plus(routeSpecification.clusterName to routeSpecification) + } + } + return when (group) { + is ServicesGroup -> { + definedServicesRoutes + definedTagsRoutes.values + } + is AllServicesGroup -> { + val allServicesRoutes = globalSnapshot.allServicesNames + .subtract(servicesNames) + .subtract(definedTagsRoutes.keys) + .map { + RouteSpecification( + clusterName = it, + routeDomains = listOf(it) + getServiceWithCustomDomain(it), + settings = group.proxySettings.outgoing.defaultServiceSettings + ) + } + allServicesRoutes + definedServicesRoutes + definedTagsRoutes.values + } + } + } + + fun domainRouteSpecifications(group: Group): Map> { + return group.proxySettings.outgoing.getDomainDependencies().groupBy( + { DomainRoutesGrouper(it.getPort(), it.useSsl()) }, + { + RouteSpecification( + clusterName = it.getClusterName(), + routeDomains = listOf(it.getRouteDomain()), + settings = it.settings + ) + } + ) + } + + fun domainPatternRouteSpecifications(group: Group): RouteSpecification { + return RouteSpecification( + clusterName = properties.dynamicForwardProxy.clusterName, + routeDomains = group.proxySettings.outgoing.getDomainPatternDependencies().map { it.domainPattern }, + settings = group.proxySettings.outgoing.defaultServiceSettings + ) + } + + private fun getServiceWithCustomDomain(it: String): List { + return if (properties.egress.domains.isNotEmpty()) { + properties.egress.domains.map { domain -> "$it$domain" } + } else { + emptyList() + } + } +} + class RouteSpecification( val clusterName: String, val routeDomains: List, diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/GlobalSnapshot.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/GlobalSnapshot.kt index ae618cd4f..a498221ee 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/GlobalSnapshot.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/GlobalSnapshot.kt @@ -9,7 +9,8 @@ data class GlobalSnapshot( val allServicesNames: Set, val endpoints: Map, val clusterConfigurations: Map, - val securedClusters: Map + val securedClusters: Map, + val tags: Map> ) @Suppress("LongParameterList") @@ -18,7 +19,8 @@ fun globalSnapshot( endpoints: Iterable = emptyList(), properties: OutgoingPermissionsProperties = OutgoingPermissionsProperties(), clusterConfigurations: Map = emptyMap(), - securedClusters: List = emptyList() + securedClusters: List = emptyList(), + tags: Map> ): GlobalSnapshot { val clusters = SnapshotResources.create(clusters, "").resources() val securedClusters = SnapshotResources.create(securedClusters, "").resources() @@ -29,7 +31,8 @@ fun globalSnapshot( securedClusters = securedClusters, endpoints = endpoints, allServicesNames = allServicesNames, - clusterConfigurations = clusterConfigurations + clusterConfigurations = clusterConfigurations, + tags = tags ) } diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotProperties.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotProperties.kt index 2caa4b906..49f7ca974 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotProperties.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotProperties.kt @@ -76,6 +76,7 @@ class OutgoingPermissionsProperties { var enabled = false var allServicesDependencies = AllServicesDependenciesProperties() var servicesAllowedToUseWildcard: MutableSet = mutableSetOf() + var tagPrefix = "" } class AllServicesDependenciesProperties { diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt index 1a25e5a7f..4506a7f54 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt @@ -110,7 +110,9 @@ class EnvoyClustersFactory( } fun getClustersForGroup(group: Group, globalSnapshot: GlobalSnapshot): List = - getEdsClustersForGroup(group, globalSnapshot) + getStrictDnsClustersForGroup(group) + clustersForJWT + + getEdsClustersForGroup(group, globalSnapshot) + + getStrictDnsClustersForGroup(group) + + clustersForJWT + getRateLimitClusterForGroup(group, globalSnapshot) private fun clusterForOAuthProvider(provider: OAuthProvider): Cluster? { @@ -190,21 +192,36 @@ class EnvoyClustersFactory( globalSnapshot.clusters } - val serviceDependencies = group.proxySettings.outgoing.getServiceDependencies().associateBy { it.service } + val serviceDependencies = group.proxySettings.outgoing.getServiceDependencies().mapNotNull { + createClusterForGroup(it.settings, clusters[it.service]) + } + val servicesNames = group.proxySettings.outgoing.getServiceDependencies().map { it.service }.toSet() + + val tagDependencies = group.proxySettings.outgoing.getTagDependencies().flatMap { tagDependency -> + globalSnapshot.tags + .filterKeys { !servicesNames.contains(it) } + .filterValues { it.contains(tagDependency.tag) } + .map { + it.key to createClusterForGroup(tagDependency.settings, clusters[it.key]) + }.toMap().asIterable() + }.fold(emptyMap()) { + acc, entry -> + if (acc.containsKey(entry.key) || entry.value == null) { + acc + } else { + acc.plus(entry.key to entry.value!!) + } + } val clustersForGroup = when (group) { - is ServicesGroup -> serviceDependencies.mapNotNull { - createClusterForGroup(it.value.settings, clusters[it.key]) - } + is ServicesGroup -> serviceDependencies + tagDependencies.values is AllServicesGroup -> { - globalSnapshot.allServicesNames.mapNotNull { - val dependency = serviceDependencies[it] - if (dependency != null && dependency.settings.timeoutPolicy.connectionIdleTimeout != null) { - createClusterForGroup(dependency.settings, clusters[it]) - } else { + globalSnapshot.allServicesNames + .subtract(servicesNames) + .subtract(tagDependencies.keys) + .mapNotNull { createClusterForGroup(group.proxySettings.outgoing.defaultServiceSettings, clusters[it]) - } - } + } + serviceDependencies + tagDependencies.values } } diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactory.kt index 18170fe85..c91401c6e 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactory.kt @@ -203,19 +203,20 @@ class EnvoyListenersFactory( { it.getPort() }, { it } ).toMap() - val httpProxy = (group.proxySettings.outgoing.getDomainDependencies() + - group.proxySettings.outgoing.getServiceDependencies()).filter { + val httpProxyPorts = (group.proxySettings.outgoing.getDomainDependencies() + + group.proxySettings.outgoing.getServiceDependencies() + + group.proxySettings.outgoing.getTagDependencies()).filter { !it.useSsl() - }.groupBy( - { it.getPort() }, { it } - ).toMutableMap() + } + .map { it.getPort() } + .toMutableSet() if (group.proxySettings.outgoing.allServicesDependencies) { - httpProxy[DEFAULT_HTTP_PORT] = group.proxySettings.outgoing.getServiceDependencies() + httpProxyPorts.add(DEFAULT_HTTP_PORT) } return createEgressTcpProxyVirtualListener(tcpProxy) + - createEgressHttpProxyVirtualListener(httpProxy.toMap(), group, globalSnapshot) + createEgressHttpProxyVirtualListener(httpProxyPorts, group, globalSnapshot) } private fun createEgressFilter( @@ -290,21 +291,21 @@ class EnvoyListenersFactory( } private fun createEgressHttpProxyVirtualListener( - portAndDomains: Map>, + portAndDomains: Set, group: Group, globalSnapshot: GlobalSnapshot ): List { - return portAndDomains.map { + return portAndDomains.map { port -> Listener.newBuilder() - .setName("$DOMAIN_PROXY_LISTENER_ADDRESS:${it.key}") + .setName("$DOMAIN_PROXY_LISTENER_ADDRESS:$port") .setAddress( Address.newBuilder().setSocketAddress( SocketAddress.newBuilder() - .setPortValue(it.key) + .setPortValue(port) .setAddress(DOMAIN_PROXY_LISTENER_ADDRESS) ) ) - .addFilterChains(createHttpProxyFilterChainForDomains(group, it.key, globalSnapshot)) + .addFilterChains(createHttpProxyFilterChainForDomains(group, port, globalSnapshot)) .setTrafficDirection(TrafficDirection.OUTBOUND) .setDeprecatedV1( Listener.DeprecatedV1.newBuilder() diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/AccessLogFilter.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/AccessLogFilter.kt index 8591841a8..d5d4848da 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/AccessLogFilter.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/AccessLogFilter.kt @@ -17,14 +17,14 @@ import io.envoyproxy.envoy.config.route.v3.HeaderMatcher import io.envoyproxy.envoy.extensions.access_loggers.file.v3.FileAccessLog import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher import pl.allegro.tech.servicemesh.envoycontrol.groups.AccessLogFilterSettings +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.AccessLogProperties import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotProperties import pl.allegro.tech.servicemesh.envoycontrol.utils.ComparisonFilterSettings import pl.allegro.tech.servicemesh.envoycontrol.utils.HeaderFilterSettings class AccessLogFilter( - snapshotProperties: SnapshotProperties + private val accessLog: AccessLogProperties ) { - private val accessLog = snapshotProperties.dynamicListeners.httpFilters.accessLog private val accessLogTimeFormat = stringValue(accessLog.timeFormat) private val accessLogMessageFormat = stringValue(accessLog.messageFormat) private val accessLogLevel = stringValue(accessLog.level) diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/HttpConnectionManagerFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/HttpConnectionManagerFactory.kt index 336ecc73d..a4b5099bc 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/HttpConnectionManagerFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/HttpConnectionManagerFactory.kt @@ -37,7 +37,7 @@ class HttpConnectionManagerFactory( ).filter private val defaultApiConfigSourceV3: ApiConfigSource = apiConfigSource() - private val accessLogFilter = AccessLogFilter(snapshotProperties) + private val accessLogFilter = AccessLogFilter(snapshotProperties.dynamicListeners.httpFilters.accessLog) @SuppressWarnings("LongParameterList") fun createFilter( diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/routes/EnvoyEgressRoutesFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/routes/EnvoyEgressRoutesFactory.kt index 5e8bb2139..eab4b3849 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/routes/EnvoyEgressRoutesFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/routes/EnvoyEgressRoutesFactory.kt @@ -11,6 +11,7 @@ import io.envoyproxy.envoy.config.route.v3.HeaderMatcher import io.envoyproxy.envoy.config.route.v3.InternalRedirectPolicy import io.envoyproxy.envoy.config.route.v3.RetryPolicy import io.envoyproxy.envoy.config.route.v3.RetryPolicy.ResetHeaderFormat +import io.envoyproxy.envoy.config.route.v3.RetryPolicy.RetryPriority import io.envoyproxy.envoy.config.route.v3.Route import io.envoyproxy.envoy.config.route.v3.RouteAction import io.envoyproxy.envoy.config.route.v3.RouteConfiguration @@ -20,12 +21,15 @@ import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher import pl.allegro.tech.servicemesh.envoycontrol.groups.RateLimitedRetryBackOff import pl.allegro.tech.servicemesh.envoycontrol.groups.RetryBackOff import pl.allegro.tech.servicemesh.envoycontrol.groups.RetryHostPredicate +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.EgressProperties +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.IncomingPermissionsProperties import pl.allegro.tech.servicemesh.envoycontrol.snapshot.RouteSpecification import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotProperties import pl.allegro.tech.servicemesh.envoycontrol.groups.RetryPolicy as EnvoyControlRetryPolicy class EnvoyEgressRoutesFactory( - private val properties: SnapshotProperties + private val egressProperties: EgressProperties, + private val incomingPermissionsProperties: IncomingPermissionsProperties ) { /** @@ -44,8 +48,8 @@ class EnvoyEgressRoutesFactory( ) .setRoute( RouteAction.newBuilder() - .setIdleTimeout(Durations.fromMillis(properties.egress.commonHttp.idleTimeout.toMillis())) - .setTimeout(Durations.fromMillis(properties.egress.commonHttp.requestTimeout.toMillis())) + .setIdleTimeout(Durations.fromMillis(egressProperties.commonHttp.idleTimeout.toMillis())) + .setTimeout(Durations.fromMillis(egressProperties.commonHttp.requestTimeout.toMillis())) .setCluster("envoy-original-destination") ) ) @@ -62,7 +66,7 @@ class EnvoyEgressRoutesFactory( ) .setDirectResponse( DirectResponseAction.newBuilder() - .setStatus(properties.egress.clusterNotFoundStatusCode) + .setStatus(egressProperties.clusterNotFoundStatusCode) ) ) .build() @@ -97,20 +101,20 @@ class EnvoyEgressRoutesFactory( .addAllVirtualHosts( virtualHosts + originalDestinationRoute + wildcardRoute ).also { - if (properties.incomingPermissions.enabled) { + if (incomingPermissionsProperties.enabled) { it.addRequestHeadersToAdd( HeaderValueOption.newBuilder() .setHeader( HeaderValue.newBuilder() - .setKey(properties.incomingPermissions.serviceNameHeader) + .setKey(incomingPermissionsProperties.serviceNameHeader) .setValue(serviceName) ).setAppend(BoolValue.of(false)) ) } } - if (properties.egress.headersToRemove.isNotEmpty()) { - routeConfiguration.addAllRequestHeadersToRemove(properties.egress.headersToRemove) + if (egressProperties.headersToRemove.isNotEmpty()) { + routeConfiguration.addAllRequestHeadersToRemove(egressProperties.headersToRemove) } if (addUpstreamAddressHeader) { @@ -210,8 +214,8 @@ class EnvoyEgressRoutesFactory( .addAllVirtualHosts( virtualHosts + originalDestinationRoute + wildcardRoute ) - if (properties.egress.headersToRemove.isNotEmpty()) { - routeConfiguration.addAllRequestHeadersToRemove(properties.egress.headersToRemove) + if (egressProperties.headersToRemove.isNotEmpty()) { + routeConfiguration.addAllRequestHeadersToRemove(egressProperties.headersToRemove) } return routeConfiguration.build() } @@ -238,8 +242,8 @@ class EnvoyEgressRoutesFactory( routeAction.internalRedirectPolicy = InternalRedirectPolicy.newBuilder().build() } - if (properties.egress.hostHeaderRewriting.enabled && routeSpecification.settings.rewriteHostHeader) { - routeAction.hostRewriteHeader = properties.egress.hostHeaderRewriting.customHostHeader + if (egressProperties.hostHeaderRewriting.enabled && routeSpecification.settings.rewriteHostHeader) { + routeAction.hostRewriteHeader = egressProperties.hostHeaderRewriting.customHostHeader } return routeAction diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/routes/EnvoyIngressRoutesFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/routes/EnvoyIngressRoutesFactory.kt index f293b9ad6..583b34ed9 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/routes/EnvoyIngressRoutesFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/routes/EnvoyIngressRoutesFactory.kt @@ -163,8 +163,8 @@ class EnvoyIngressRoutesFactory( addAllRetriableStatusCodes(retryProps.retriableStatusCodes) }.build() - val defaultRetryPolicy: RetryPolicy = retryPolicy(properties.localService.retryPolicy.default) - val perMethodRetryPolicies: Map = properties.localService.retryPolicy.perHttpMethod + private val defaultRetryPolicy: RetryPolicy = retryPolicy(properties.localService.retryPolicy.default) + private val perMethodRetryPolicies: Map = properties.localService.retryPolicy.perHttpMethod .filter { it.value.enabled } .map { HttpMethod.valueOf(it.key) to retryPolicy(it.value) } .toMap() diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/EnvoySnapshotFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/EnvoySnapshotFactoryTest.kt index 41fe92da5..f550024e4 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/EnvoySnapshotFactoryTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/EnvoySnapshotFactoryTest.kt @@ -27,6 +27,7 @@ import pl.allegro.tech.servicemesh.envoycontrol.groups.ServicesGroup import pl.allegro.tech.servicemesh.envoycontrol.groups.with import pl.allegro.tech.servicemesh.envoycontrol.snapshot.EnvoySnapshotFactory import pl.allegro.tech.servicemesh.envoycontrol.snapshot.GlobalSnapshot +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.RouteSpecificationFactory import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotProperties import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotsVersions import pl.allegro.tech.servicemesh.envoycontrol.snapshot.outgoingTimeoutPolicy @@ -353,6 +354,7 @@ class EnvoySnapshotFactoryTest { val endpointsFactory = EnvoyEndpointsFactory(properties, ServiceTagMetadataGenerator()) val envoyHttpFilters = EnvoyHttpFilters.defaultFilters(properties) val listenersFactory = EnvoyListenersFactory(properties, envoyHttpFilters) + val routeSpecificationFactory = RouteSpecificationFactory(properties) val snapshotsVersions = SnapshotsVersions() val meterRegistry: MeterRegistry = SimpleMeterRegistry() @@ -362,6 +364,7 @@ class EnvoySnapshotFactoryTest { clustersFactory, endpointsFactory, listenersFactory, + routeSpecificationFactory, snapshotsVersions, properties, meterRegistry @@ -374,7 +377,8 @@ class EnvoySnapshotFactoryTest { clusters.map { it.name }.toSet(), SnapshotResources.create(emptyList(), "v1").resources(), emptyMap(), - SnapshotResources.create(clusters.toList(), "v3").resources() + SnapshotResources.create(clusters.toList(), "v3").resources(), + emptyMap() ) } @@ -400,3 +404,12 @@ class EnvoySnapshotFactoryTest { .build() } } + +class RouteSpecificationFactoryTest { + + @Test + fun `a`() { + + } + +} diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataTest.kt index 526bd9cae..856040478 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataTest.kt @@ -54,21 +54,6 @@ class NodeMetadataTest { ) } - private fun snapshotProperties( - allServicesDependenciesIdentifier: String = "*", - handleInternalRedirect: Boolean = false, - idleTimeout: String = "120s", - connectionIdleTimeout: String = "120s", - requestTimeout: String = "120s" - ) = SnapshotProperties().apply { - outgoingPermissions.allServicesDependencies.identifier = allServicesDependenciesIdentifier - egress.handleInternalRedirect = handleInternalRedirect - egress.commonHttp.idleTimeout = Duration.ofNanos(Durations.toNanos(Durations.parse(idleTimeout))) - egress.commonHttp.connectionIdleTimeout = - Duration.ofNanos(Durations.toNanos(Durations.parse(connectionIdleTimeout))) - egress.commonHttp.requestTimeout = Duration.ofNanos(Durations.toNanos(Durations.parse(requestTimeout))) - } - @Test fun `should reject endpoint with both path and pathPrefix defined`() { // given @@ -270,82 +255,6 @@ class NodeMetadataTest { assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) } - @Test - fun `should reject dependency with unsupported protocol in domain field `() { - // given - val proto = outgoingDependenciesProto { - withDomain(url = "ftp://domain") - } - - // expects - val exception = assertThrows { proto.toOutgoing(snapshotProperties()) } - assertThat(exception.status.description) - .isEqualTo("Unsupported protocol for domain dependency for domain ftp://domain") - assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) - } - - @Test - fun `should reject domain dependency with unsupported all services dependencies identifier`() { - // given - val proto = outgoingDependenciesProto { - withDomain(url = "*") - } - val properties = snapshotProperties(allServicesDependenciesIdentifier = "*") - - // expects - val exception = assertThrows { proto.toOutgoing(properties) } - assertThat(exception.status.description) - .isEqualTo("Unsupported 'all serviceDependencies identifier' for domain dependency: *") - assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) - } - - @Test - fun `should accept domain dependency`() { - // given - val proto = outgoingDependenciesProto { - withDomain(url = "http://domain") - } - - // when - val outgoing = proto.toOutgoing(snapshotProperties()) - - // expects - val dependency = outgoing.getDomainDependencies().single() - assertThat(dependency.domain).isEqualTo("http://domain") - } - - @Test - fun `should return correct host and default port for domain dependency`() { - // given - val proto = outgoingDependenciesProto { - withDomain(url = "http://domain") - } - - // when - val outgoing = proto.toOutgoing(snapshotProperties()) - - // expects - val dependency = outgoing.getDomainDependencies().single() - assertThat(dependency.getHost()).isEqualTo("domain") - assertThat(dependency.getPort()).isEqualTo(80) - } - - @Test - fun `should return correct host and default port for https domain dependency`() { - // given - val proto = outgoingDependenciesProto { - withDomain(url = "https://domain") - } - - // when - val outgoing = proto.toOutgoing(snapshotProperties()) - - // expects - val dependency = outgoing.getDomainDependencies().single() - assertThat(dependency.getHost()).isEqualTo("domain") - assertThat(dependency.getPort()).isEqualTo(443) - } - @Test fun `should return retry policy`() { // given @@ -438,217 +347,425 @@ class NodeMetadataTest { } @Test - fun `should deduplicate domains dependencies based on url`() { + fun `should accept incoming settings with custom healthCheckPath`() { // given - val proto = outgoingDependenciesProto { - withDomain(url = "http://domain", requestTimeout = "8s", idleTimeout = "8s") - withDomain(url = "http://domain", requestTimeout = "10s", idleTimeout = "10s", connectionIdleTimeout = "5s") - withDomain(url = "http://domain2") - } + val proto = proxySettingsProto( + incomingSettings = true, + path = "/path", + healthCheckPath = "/status/ping" + ) + val incoming = proto.structValue?.fieldsMap?.get("incoming").toIncoming(snapshotProperties()) - // when - val outgoing = proto.toOutgoing(snapshotProperties()) + // expects + assertThat(incoming.healthCheck.clusterName).isEqualTo("local_service_health_check") + assertThat(incoming.healthCheck.path).isEqualTo("/status/ping") + assertThat(incoming.healthCheck.hasCustomHealthCheck()).isTrue() + } + + @Test + fun `should set empty healthCheckPath for incoming settings when healthCheckPath is empty`() { + // given + val proto = proxySettingsProto( + incomingSettings = true, + path = "/path" + ) + val incoming = proto.structValue?.fieldsMap?.get("incoming").toIncoming(snapshotProperties()) // expects - val dependencies = outgoing.getDomainDependencies() - assertThat(dependencies).hasSize(2) - assertThat(dependencies[0].getHost()).isEqualTo("domain") - assertThat(dependencies[0].getPort()).isEqualTo(80) - assertThat(dependencies[0].settings).hasTimeouts( - idleTimeout = "10s", - requestTimeout = "10s", - connectionIdleTimeout = "5s" + assertThat(incoming.healthCheck.clusterName).isEqualTo("local_service_health_check") + assertThat(incoming.healthCheck.path).isEqualTo("") + assertThat(incoming.healthCheck.hasCustomHealthCheck()).isFalse() + } + + @Test + fun `should set healthCheckPath and healthCheckClusterName for incoming settings`() { + // given + val proto = proxySettingsProto( + incomingSettings = true, + path = "/path", + healthCheckPath = "/status/ping", + healthCheckClusterName = "local_service_health_check" ) - assertThat(dependencies[1].getHost()).isEqualTo("domain2") - assertThat(dependencies[1].getPort()).isEqualTo(80) + val incoming = proto.structValue?.fieldsMap?.get("incoming").toIncoming(snapshotProperties()) + + // expects + assertThat(incoming.healthCheck.clusterName).isEqualTo("local_service_health_check") + assertThat(incoming.healthCheck.path).isEqualTo("/status/ping") + assertThat(incoming.healthCheck.hasCustomHealthCheck()).isTrue() } @Test - fun `should deduplicate services dependencies based on serviceName`() { + fun `should reject configuration with number timeout format`() { // given - val proto = outgoingDependenciesProto { - withService(serviceName = "service-1", requestTimeout = "8s", idleTimeout = "8s") - withService( - serviceName = "service-1", - requestTimeout = "10s", - idleTimeout = "10s", - connectionIdleTimeout = "10s" - ) - withService(serviceName = "service-2") - } + val proto = Value.newBuilder().setNumberValue(10.0).build() // when - val outgoing = proto.toOutgoing(snapshotProperties()) + val exception = assertThrows { proto.toDuration() } - // expect - val dependencies = outgoing.getServiceDependencies() - assertThat(dependencies).hasSize(2) - assertThat(dependencies[0].service).isEqualTo("service-1") - assertThat(dependencies[0].settings).hasTimeouts( - idleTimeout = "10s", - requestTimeout = "10s", - connectionIdleTimeout = "10s" + // then + assertThat(exception.status.description).isEqualTo( + "Timeout definition has number format" + + " but should be in string format and ends with 's'" ) - assertThat(dependencies[1].service).isEqualTo("service-2") + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) } @Test - fun `should return custom port for domain dependency if it was defined`() { + fun `should reject configuration with incorrect string timeout format`() { // given - val proto = outgoingDependenciesProto { - withDomain(url = "http://domain:1234") - } + val proto = Value.newBuilder().setStringValue("20").build() // when - val outgoing = proto.toOutgoing(snapshotProperties()) + val exception = assertThrows { proto.toDuration() } - // expects - val dependency = outgoing.getDomainDependencies().single() - assertThat(dependency.getPort()).isEqualTo(1234) + // then + assertThat(exception.status.description).isEqualTo( + "Timeout definition has incorrect format: " + + "Invalid duration string: 20" + ) + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) } @Test - fun `should return correct names for domain dependency without port specified`() { + fun `should return duration when correct configuration provided`() { // given - val proto = outgoingDependenciesProto { - withDomain(url = "http://domain.pl") - } + val proto = Value.newBuilder().setStringValue("20s").build() // when - val outgoing = proto.toOutgoing(snapshotProperties()) + val duration = proto.toDuration() - // expects - val dependency = outgoing.getDomainDependencies().single() - assertThat(dependency.getClusterName()).isEqualTo("domain_pl_80") - assertThat(dependency.getRouteDomain()).isEqualTo("domain.pl") + // then + assertThat(duration).isNotNull + assertThat(duration!!.seconds).isEqualTo(20L) } @Test - fun `should return correct names for domain dependency with port specified`() { + fun `should return null when empty value provided`() { // given - val proto = outgoingDependenciesProto { - withDomain(url = "http://domain.pl:80") - } + val proto = Value.newBuilder().build() // when - val outgoing = proto.toOutgoing(snapshotProperties()) + val duration = proto.toDuration() - // expects - val dependency = outgoing.getDomainDependencies().single() - assertThat(dependency.getClusterName()).isEqualTo("domain_pl_80") - assertThat(dependency.getRouteDomain()).isEqualTo("domain.pl:80") + // then + assertThat(duration).isNull() } - @Test - fun `should accept domain pattern dependency`() { + @ParameterizedTest + @MethodSource("validComparisonFilterData") + fun `should set statusCodeFilter for accessLogFilter`(input: String, op: ComparisonFilter.Op, code: Int) { // given - val proto = outgoingDependenciesProto { - withDomainPattern(pattern = "*.example.com") - } + val proto = accessLogFilterProto(value = input, fieldName = "status_code_filter") // when - val outgoing = proto.toOutgoing(snapshotProperties()) + val statusCodeFilterSettings = proto.structValue?.fieldsMap?.get("status_code_filter").toComparisonFilter() // expects - val dependency = outgoing.getDomainPatternDependencies().single() - assertThat(dependency.domainPattern).isEqualTo("*.example.com") + assertThat(statusCodeFilterSettings?.comparisonCode).isEqualTo(code) + assertThat(statusCodeFilterSettings?.comparisonOperator).isEqualTo(op) } - @Test - fun `should reject domain pattern dependency with schema`() { + @ParameterizedTest + @MethodSource("invalidComparisonFilterData") + fun `should throw exception for invalid status code filter data`(input: String) { // given - val proto = outgoingDependenciesProto { - withDomainPattern(pattern = "http://example.com") - } + val proto = accessLogFilterProto(value = input, fieldName = "status_code_filter") // expects - val exception = assertThrows { proto.toOutgoing(snapshotProperties()) } - assertThat(exception.status.description).isEqualTo( - "Unsupported format for domainPattern: domainPattern cannot contain a schema like http:// or https://" - ) + val exception = assertThrows { + proto.structValue?.fieldsMap?.get("status_code_filter").toComparisonFilter() + } + assertThat(exception.status.description) + .isEqualTo("Invalid access log comparison filter. Expected OPERATOR:VALUE") assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) } - @Test - fun `should accept service dependency with redirect policy defined`() { + @ParameterizedTest + @MethodSource("errorMessages") + fun `should throw exception for null value comparison filter data`(filter: String, errorMessage: String) { // given - val proto = outgoingDependenciesProto { - withService(serviceName = "service-1", handleInternalRedirect = true) - } - - // when - val outgoing = proto.toOutgoing(snapshotProperties()) + val proto = accessLogFilterProto(value = null, fieldName = filter) - // expect - val dependency = outgoing.getServiceDependencies().single() - assertThat(dependency.service).isEqualTo("service-1") - assertThat(dependency.settings.handleInternalRedirect).isEqualTo(true) + // expects + val exception = assertThrows { + proto.structValue?.fieldsMap?.get(filter).toComparisonFilter() + } + assertThat(exception.status.description) + .isEqualTo(errorMessage) + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + } + + @ParameterizedTest + @MethodSource("validComparisonFilterData") + fun `should set duration filter for accessLogFilter`(input: String, op: ComparisonFilter.Op, code: Int) { + // given + val proto = accessLogFilterProto(value = input, fieldName = "duration_filter") + + // when + val durationFilterSettings = proto.structValue?.fieldsMap?.get("duration_filter").toComparisonFilter() + + // expects + assertThat(durationFilterSettings?.comparisonCode).isEqualTo(code) + assertThat(durationFilterSettings?.comparisonOperator).isEqualTo(op) + } + + @ParameterizedTest + @MethodSource("invalidComparisonFilterData") + fun `should throw exception for invalid duration filter data`(input: String) { + // given + val proto = accessLogFilterProto(value = input, fieldName = "duration_filter") + + // expects + val exception = assertThrows { + proto.structValue?.fieldsMap?.get("duration_filter").toComparisonFilter() + } + assertThat(exception.status.description) + .isEqualTo("Invalid access log comparison filter. Expected OPERATOR:VALUE") + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) } @Test - fun `should accept incoming settings with custom healthCheckPath`() { + fun `should throw exception for null value header filter data`() { // given - val proto = proxySettingsProto( - incomingSettings = true, - path = "/path", - healthCheckPath = "/status/ping" + val proto = accessLogFilterProto(value = null, fieldName = "header_filter") + + // expects + val exception = assertThrows { + proto.structValue?.fieldsMap?.get("header_filter").toHeaderFilter() + } + assertThat(exception.status.description) + .isEqualTo("Invalid access log header filter. Expected HEADER_NAME:REGEX") + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + } + + @Test + fun `should set header filter for accessLogFilter`() { + // given + val proto = accessLogFilterProto(value = "test:^((.+):(.+))$", fieldName = "header_filter") + + // when + val headerFilterSettings = proto.structValue?.fieldsMap?.get("header_filter").toHeaderFilter() + + // expects + assertThat(headerFilterSettings?.headerName).isEqualTo("test") + assertThat(headerFilterSettings?.regex).isEqualTo("^((.+):(.+))\$") + } + + @Test + fun `should throw exception for invalid header filter data`() { + // given + val proto = accessLogFilterProto(value = "test;test", fieldName = "header_filter") + + // expects + val exception = assertThrows { + proto.structValue?.fieldsMap?.get("header_filter").toHeaderFilter() + } + assertThat(exception.status.description) + .isEqualTo("Invalid access log header filter. Expected HEADER_NAME:REGEX") + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + } + + @Test + fun `should set response flag filter for accessLogFilter`() { + // given + val availableFlags = listOf( + "UH", "UF", "UO", "NR", "URX", "NC", "DT", "DC", "LH", "UT", "LR", "UR", + "UC", "DI", "FI", "RL", "UAEX", "RLSE", "IH", "SI", "DPE", "UPE", "UMSDR", + "OM", "DF" ) - val incoming = proto.structValue?.fieldsMap?.get("incoming").toIncoming(snapshotProperties()) + val proto = accessLogFilterProto(value = availableFlags.joinToString(","), fieldName = "response_flag_filter") + + // when + val responseFlags = proto.structValue?.fieldsMap?.get("response_flag_filter").toResponseFlagFilter() // expects - assertThat(incoming.healthCheck.clusterName).isEqualTo("local_service_health_check") - assertThat(incoming.healthCheck.path).isEqualTo("/status/ping") - assertThat(incoming.healthCheck.hasCustomHealthCheck()).isTrue() + assertThat(responseFlags).isEqualTo(availableFlags) } @Test - fun `should parse allServiceDependency with timeouts configuration`() { + fun `should throw exception for invalid response flag filter data`() { + // given + val availableFlagsAndInvalid = listOf( + "UH", "UF", "UO", "NR", "URX", "NC", "DT", "DC", "LH", "UT", "LR", "UR", + "UC", "DI", "FI", "RL", "UAEX", "RLSE", "IH", "SI", "DPE", "UPE", "UMSDR", + "OM", "DF", "invalid" + ) + val proto = + accessLogFilterProto(value = availableFlagsAndInvalid.joinToString(","), fieldName = "response_flag_filter") + + // expects + val exception = assertThrows { + proto.structValue?.fieldsMap?.get("response_flag_filter").toResponseFlagFilter() + } + assertThat(exception.status.description) + .isEqualTo("Invalid access log response flag filter. Expected valid values separated by comma") + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + } + + @Test + fun `should set not health check filter for accessLogFilter`() { + // given + val proto = accessLogBooleanFilterProto(value = true, fieldName = "not_health_check_filter") + + // when + val value = proto.structValue?.fieldsMap?.get("not_health_check_filter")?.boolValue + + // expects + assertThat(value).isEqualTo(true) + } + + @Test + fun `should throw exception for null value response flag filter data`() { + // given + val proto = accessLogFilterProto(value = null, fieldName = "response_flag_filter") + + // expects + val exception = assertThrows { + proto.structValue?.fieldsMap?.get("response_flag_filter").toResponseFlagFilter() + } + assertThat(exception.status.description) + .isEqualTo("Invalid access log response flag filter. Expected valid values separated by comma") + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + } + + private fun createJwtSnapshotProperties(): SnapshotProperties { + val snapshotProperties = SnapshotProperties() + val jwtFilterProperties = JwtFilterProperties() + val oauthProviders = mapOf( + "oauth2-mock" to + OAuthProvider( + jwksUri = URI.create("http://localhost:8080/jwks-address/"), + clusterName = "oauth" + ) + ) + jwtFilterProperties.providers = oauthProviders + snapshotProperties.jwt = jwtFilterProperties + + return snapshotProperties + } +} + +class DomainDependencyTest { + + @Test + fun `should accept domain dependency`() { // given val proto = outgoingDependenciesProto { - withService(serviceName = "*", idleTimeout = "10s", requestTimeout = "10s", connectionIdleTimeout = "10s") + withDomain(url = "http://domain") } // when - val outgoing = proto.toOutgoing(snapshotProperties(allServicesDependenciesIdentifier = "*")) + val outgoing = proto.toOutgoing(snapshotProperties()) // expects - assertThat(outgoing.allServicesDependencies).isTrue() - assertThat(outgoing.defaultServiceSettings).hasTimeouts( + val dependency = outgoing.getDomainDependencies().single() + assertThat(dependency.domain).isEqualTo("http://domain") + } + + @Test + fun `should deduplicate domains dependencies based on url`() { + // given + val proto = outgoingDependenciesProto { + withDomain(url = "http://domain", requestTimeout = "8s", idleTimeout = "8s") + withDomain(url = "http://domain", requestTimeout = "10s", idleTimeout = "10s", connectionIdleTimeout = "5s") + withDomain(url = "http://domain2") + } + + // when + val outgoing = proto.toOutgoing(snapshotProperties()) + + // expects + val dependencies = outgoing.getDomainDependencies() + assertThat(dependencies).hasSize(2) + assertThat(dependencies[0].getHost()).isEqualTo("domain") + assertThat(dependencies[0].getPort()).isEqualTo(80) + assertThat(dependencies[0].settings).hasTimeouts( idleTimeout = "10s", requestTimeout = "10s", - connectionIdleTimeout = "10s" + connectionIdleTimeout = "5s" ) - assertThat(outgoing.getServiceDependencies()).isEmpty() + assertThat(dependencies[1].getHost()).isEqualTo("domain2") + assertThat(dependencies[1].getPort()).isEqualTo(80) } @Test - fun `should parse allServiceDependency and use requestTimeout from properties`() { + fun `should return custom port for domain dependency if it was defined`() { // given val proto = outgoingDependenciesProto { - withService(serviceName = "*", idleTimeout = "10s", requestTimeout = null, connectionIdleTimeout = "10s") + withDomain(url = "http://domain:1234") } - val properties = snapshotProperties(allServicesDependenciesIdentifier = "*", requestTimeout = "5s") + // when + val outgoing = proto.toOutgoing(snapshotProperties()) - val outgoing = proto.toOutgoing(properties) + // expects + val dependency = outgoing.getDomainDependencies().single() + assertThat(dependency.getPort()).isEqualTo(1234) + } + + @Test + fun `should return correct names for domain dependency without port specified`() { + // given + val proto = outgoingDependenciesProto { + withDomain(url = "http://domain.pl") + } + + // when + val outgoing = proto.toOutgoing(snapshotProperties()) // expects - assertThat(outgoing.allServicesDependencies).isTrue() - assertThat(outgoing.defaultServiceSettings).hasTimeouts( - idleTimeout = "10s", - requestTimeout = "5s", - connectionIdleTimeout = "10s" - ) - assertThat(outgoing.getServiceDependencies()).isEmpty() + val dependency = outgoing.getDomainDependencies().single() + assertThat(dependency.getClusterName()).isEqualTo("domain_pl_80") + assertThat(dependency.getRouteDomain()).isEqualTo("domain.pl") } @Test - fun `should parse allServiceDependency and use idleTimeout from properties`() { + fun `should return correct names for domain dependency with port specified`() { // given val proto = outgoingDependenciesProto { - withService(serviceName = "*", idleTimeout = null, requestTimeout = "10s", connectionIdleTimeout = "10s") + withDomain(url = "http://domain.pl:80") } - val properties = snapshotProperties(allServicesDependenciesIdentifier = "*", idleTimeout = "5s") + + // when + val outgoing = proto.toOutgoing(snapshotProperties()) + + // expects + val dependency = outgoing.getDomainDependencies().single() + assertThat(dependency.getClusterName()).isEqualTo("domain_pl_80") + assertThat(dependency.getRouteDomain()).isEqualTo("domain.pl:80") + } + + @Test + fun `should parse domain dependencies and for missing config use config defined in properties even if allServiceDependency is defined`() { + // given + val proto = outgoingDependenciesProto { + withService(serviceName = "*", idleTimeout = "10s", requestTimeout = "10s", connectionIdleTimeout = "10s") + withDomain( + url = "http://domain-name-1", + idleTimeout = "5s", + requestTimeout = null, + connectionIdleTimeout = null + ) + withDomain( + url = "http://domain-name-2", + idleTimeout = null, + requestTimeout = "4s", + connectionIdleTimeout = null + ) + withDomain( + url = "http://domain-name-3", + idleTimeout = null, + requestTimeout = null, + connectionIdleTimeout = "3s" + ) + withDomain( + url = "http://domain-name-4", + idleTimeout = null, + requestTimeout = null, + connectionIdleTimeout = null + ) + } + val properties = snapshotProperties(idleTimeout = "12s", requestTimeout = "12s", connectionIdleTimeout = "12s") // when val outgoing = proto.toOutgoing(properties) @@ -656,59 +773,119 @@ class NodeMetadataTest { // expects assertThat(outgoing.allServicesDependencies).isTrue() assertThat(outgoing.defaultServiceSettings).hasTimeouts( - idleTimeout = "5s", + idleTimeout = "10s", requestTimeout = "10s", connectionIdleTimeout = "10s" ) - assertThat(outgoing.getServiceDependencies()).isEmpty() + + outgoing.getDomainDependencies().assertDomainDependency("http://domain-name-1") + .hasTimeouts(idleTimeout = "5s", requestTimeout = "12s", connectionIdleTimeout = "12s") + outgoing.getDomainDependencies().assertDomainDependency("http://domain-name-2") + .hasTimeouts(idleTimeout = "12s", requestTimeout = "4s", connectionIdleTimeout = "12s") + outgoing.getDomainDependencies().assertDomainDependency("http://domain-name-3") + .hasTimeouts(idleTimeout = "12s", requestTimeout = "12s", connectionIdleTimeout = "3s") + outgoing.getDomainDependencies().assertDomainDependency("http://domain-name-4") + .hasTimeouts(idleTimeout = "12s", requestTimeout = "12s", connectionIdleTimeout = "12s") + } + + @Test + fun `should reject dependency with unsupported protocol in domain field `() { + // given + val proto = outgoingDependenciesProto { + withDomain(url = "ftp://domain") + } + + // expects + val exception = assertThrows { proto.toOutgoing(snapshotProperties()) } + assertThat(exception.status.description) + .isEqualTo("Unsupported protocol for domain dependency for domain ftp://domain") + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + } + + @Test + fun `should return correct host and default port for domain dependency`() { + // given + val proto = outgoingDependenciesProto { + withDomain(url = "http://domain") + } + + // when + val outgoing = proto.toOutgoing(snapshotProperties()) + + // expects + val dependency = outgoing.getDomainDependencies().single() + assertThat(dependency.getHost()).isEqualTo("domain") + assertThat(dependency.getPort()).isEqualTo(80) + } + + @Test + fun `should return correct host and default port for https domain dependency`() { + // given + val proto = outgoingDependenciesProto { + withDomain(url = "https://domain") + } + + // when + val outgoing = proto.toOutgoing(snapshotProperties()) + + // expects + val dependency = outgoing.getDomainDependencies().single() + assertThat(dependency.getHost()).isEqualTo("domain") + assertThat(dependency.getPort()).isEqualTo(443) } + fun List.assertDomainDependency(name: String): ObjectAssert { + val list = this.filter { it.domain == name } + assertThat(list).hasSize(1) + val single = list.single().settings + return assertThat(single) + } +} + +class ServiceDependencyTest { @Test - fun `should parse allServiceDependency and use connectionIdleTimeout from properties`() { + fun `should deduplicate services dependencies based on serviceName`() { // given val proto = outgoingDependenciesProto { - withService(serviceName = "*", idleTimeout = "10s", requestTimeout = "10s", connectionIdleTimeout = null) + withService(serviceName = "service-1", requestTimeout = "8s", idleTimeout = "8s") + withService( + serviceName = "service-1", + requestTimeout = "10s", + idleTimeout = "10s", + connectionIdleTimeout = "10s" + ) + withService(serviceName = "service-2") } - val properties = snapshotProperties(allServicesDependenciesIdentifier = "*", connectionIdleTimeout = "5s") - // when - val outgoing = proto.toOutgoing(properties) + // when + val outgoing = proto.toOutgoing(snapshotProperties()) - // expects - assertThat(outgoing.allServicesDependencies).isTrue() - assertThat(outgoing.defaultServiceSettings).hasTimeouts( + // expect + val dependencies = outgoing.getServiceDependencies() + assertThat(dependencies).hasSize(2) + assertThat(dependencies[0].service).isEqualTo("service-1") + assertThat(dependencies[0].settings).hasTimeouts( idleTimeout = "10s", requestTimeout = "10s", - connectionIdleTimeout = "5s" + connectionIdleTimeout = "10s" ) - assertThat(outgoing.getServiceDependencies()).isEmpty() + assertThat(dependencies[1].service).isEqualTo("service-2") } @Test - fun `should parse allServiceDependency and use timeouts from properties`() { + fun `should accept service dependency with redirect policy defined`() { // given val proto = outgoingDependenciesProto { - withService(serviceName = "*", idleTimeout = null, requestTimeout = null) + withService(serviceName = "service-1", handleInternalRedirect = true) } - val properties = - snapshotProperties( - allServicesDependenciesIdentifier = "*", - idleTimeout = "5s", - requestTimeout = "5s", - connectionIdleTimeout = "5s" - ) - // when - val outgoing = proto.toOutgoing(properties) + // when + val outgoing = proto.toOutgoing(snapshotProperties()) - // expects - assertThat(outgoing.allServicesDependencies).isTrue() - assertThat(outgoing.defaultServiceSettings).hasTimeouts( - idleTimeout = "5s", - requestTimeout = "5s", - connectionIdleTimeout = "5s" - ) - assertThat(outgoing.getServiceDependencies()).isEmpty() + // expect + val dependency = outgoing.getServiceDependencies().single() + assertThat(dependency.service).isEqualTo("service-1") + assertThat(dependency.settings.handleInternalRedirect).isEqualTo(true) } @Test @@ -816,40 +993,24 @@ class NodeMetadataTest { .hasTimeouts(idleTimeout = "12s", requestTimeout = "12s", connectionIdleTimeout = "12s") } + fun List.assertServiceDependency(name: String): ObjectAssert { + val list = this.filter { it.service == name } + assertThat(list).hasSize(1) + val single = list.single().settings + return assertThat(single) + } +} + +class AllServiceDependencyTest { @Test - fun `should parse domain dependencies and for missing config use config defined in properties even if allServiceDependency is defined`() { + fun `should parse allServiceDependency with timeouts configuration`() { // given val proto = outgoingDependenciesProto { withService(serviceName = "*", idleTimeout = "10s", requestTimeout = "10s", connectionIdleTimeout = "10s") - withDomain( - url = "http://domain-name-1", - idleTimeout = "5s", - requestTimeout = null, - connectionIdleTimeout = null - ) - withDomain( - url = "http://domain-name-2", - idleTimeout = null, - requestTimeout = "4s", - connectionIdleTimeout = null - ) - withDomain( - url = "http://domain-name-3", - idleTimeout = null, - requestTimeout = null, - connectionIdleTimeout = "3s" - ) - withDomain( - url = "http://domain-name-4", - idleTimeout = null, - requestTimeout = null, - connectionIdleTimeout = null - ) } - val properties = snapshotProperties(idleTimeout = "12s", requestTimeout = "12s", connectionIdleTimeout = "12s") - // when - val outgoing = proto.toOutgoing(properties) + // when + val outgoing = proto.toOutgoing(snapshotProperties(allServicesDependenciesIdentifier = "*")) // expects assertThat(outgoing.allServicesDependencies).isTrue() @@ -858,341 +1019,349 @@ class NodeMetadataTest { requestTimeout = "10s", connectionIdleTimeout = "10s" ) - - outgoing.getDomainDependencies().assertDomainDependency("http://domain-name-1") - .hasTimeouts(idleTimeout = "5s", requestTimeout = "12s", connectionIdleTimeout = "12s") - outgoing.getDomainDependencies().assertDomainDependency("http://domain-name-2") - .hasTimeouts(idleTimeout = "12s", requestTimeout = "4s", connectionIdleTimeout = "12s") - outgoing.getDomainDependencies().assertDomainDependency("http://domain-name-3") - .hasTimeouts(idleTimeout = "12s", requestTimeout = "12s", connectionIdleTimeout = "3s") - outgoing.getDomainDependencies().assertDomainDependency("http://domain-name-4") - .hasTimeouts(idleTimeout = "12s", requestTimeout = "12s", connectionIdleTimeout = "12s") + assertThat(outgoing.getServiceDependencies()).isEmpty() } @Test - fun `should throw exception when there are multiple allServiceDependency`() { + fun `should parse allServiceDependency and use requestTimeout from properties`() { // given val proto = outgoingDependenciesProto { - withServices(serviceDependencies = listOf("*", "*", "a")) - } - - // expects - val exception = assertThrows { - proto.toOutgoing(snapshotProperties(allServicesDependenciesIdentifier = "*")) + withService(serviceName = "*", idleTimeout = "10s", requestTimeout = null, connectionIdleTimeout = "10s") } - assertThat(exception.status.description) - .isEqualTo("Define at most one 'all serviceDependencies identifier' as an service dependency") - assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) - } - - @Test - fun `should set empty healthCheckPath for incoming settings when healthCheckPath is empty`() { - // given - val proto = proxySettingsProto( - incomingSettings = true, - path = "/path" - ) - val incoming = proto.structValue?.fieldsMap?.get("incoming").toIncoming(snapshotProperties()) - - // expects - assertThat(incoming.healthCheck.clusterName).isEqualTo("local_service_health_check") - assertThat(incoming.healthCheck.path).isEqualTo("") - assertThat(incoming.healthCheck.hasCustomHealthCheck()).isFalse() - } - - @Test - fun `should set healthCheckPath and healthCheckClusterName for incoming settings`() { - // given - val proto = proxySettingsProto( - incomingSettings = true, - path = "/path", - healthCheckPath = "/status/ping", - healthCheckClusterName = "local_service_health_check" - ) - val incoming = proto.structValue?.fieldsMap?.get("incoming").toIncoming(snapshotProperties()) - - // expects - assertThat(incoming.healthCheck.clusterName).isEqualTo("local_service_health_check") - assertThat(incoming.healthCheck.path).isEqualTo("/status/ping") - assertThat(incoming.healthCheck.hasCustomHealthCheck()).isTrue() - } - - @Test - fun `should reject configuration with number timeout format`() { - // given - val proto = Value.newBuilder().setNumberValue(10.0).build() - + val properties = snapshotProperties(allServicesDependenciesIdentifier = "*", requestTimeout = "5s") // when - val exception = assertThrows { proto.toDuration() } - - // then - assertThat(exception.status.description).isEqualTo( - "Timeout definition has number format" + - " but should be in string format and ends with 's'" - ) - assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) - } - - @Test - fun `should reject configuration with incorrect string timeout format`() { - // given - val proto = Value.newBuilder().setStringValue("20").build() - // when - val exception = assertThrows { proto.toDuration() } + val outgoing = proto.toOutgoing(properties) - // then - assertThat(exception.status.description).isEqualTo( - "Timeout definition has incorrect format: " + - "Invalid duration string: 20" + // expects + assertThat(outgoing.allServicesDependencies).isTrue() + assertThat(outgoing.defaultServiceSettings).hasTimeouts( + idleTimeout = "10s", + requestTimeout = "5s", + connectionIdleTimeout = "10s" ) - assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) - } - - @Test - fun `should return duration when correct configuration provided`() { - // given - val proto = Value.newBuilder().setStringValue("20s").build() - - // when - val duration = proto.toDuration() - - // then - assertThat(duration).isNotNull - assertThat(duration!!.seconds).isEqualTo(20L) + assertThat(outgoing.getServiceDependencies()).isEmpty() } @Test - fun `should return null when empty value provided`() { - // given - val proto = Value.newBuilder().build() - - // when - val duration = proto.toDuration() - - // then - assertThat(duration).isNull() - } - - @ParameterizedTest - @MethodSource("validComparisonFilterData") - fun `should set statusCodeFilter for accessLogFilter`(input: String, op: ComparisonFilter.Op, code: Int) { + fun `should parse allServiceDependency and use idleTimeout from properties`() { // given - val proto = accessLogFilterProto(value = input, fieldName = "status_code_filter") - + val proto = outgoingDependenciesProto { + withService(serviceName = "*", idleTimeout = null, requestTimeout = "10s", connectionIdleTimeout = "10s") + } + val properties = snapshotProperties(allServicesDependenciesIdentifier = "*", idleTimeout = "5s") // when - val statusCodeFilterSettings = proto.structValue?.fieldsMap?.get("status_code_filter").toComparisonFilter() - - // expects - assertThat(statusCodeFilterSettings?.comparisonCode).isEqualTo(code) - assertThat(statusCodeFilterSettings?.comparisonOperator).isEqualTo(op) - } - @ParameterizedTest - @MethodSource("invalidComparisonFilterData") - fun `should throw exception for invalid status code filter data`(input: String) { - // given - val proto = accessLogFilterProto(value = input, fieldName = "status_code_filter") + val outgoing = proto.toOutgoing(properties) // expects - val exception = assertThrows { - proto.structValue?.fieldsMap?.get("status_code_filter").toComparisonFilter() - } - assertThat(exception.status.description) - .isEqualTo("Invalid access log comparison filter. Expected OPERATOR:VALUE") - assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(outgoing.allServicesDependencies).isTrue() + assertThat(outgoing.defaultServiceSettings).hasTimeouts( + idleTimeout = "5s", + requestTimeout = "10s", + connectionIdleTimeout = "10s" + ) + assertThat(outgoing.getServiceDependencies()).isEmpty() } - - @ParameterizedTest - @MethodSource("errorMessages") - fun `should throw exception for null value comparison filter data`(filter: String, errorMessage: String) { + + @Test + fun `should parse allServiceDependency and use connectionIdleTimeout from properties`() { // given - val proto = accessLogFilterProto(value = null, fieldName = filter) + val proto = outgoingDependenciesProto { + withService(serviceName = "*", idleTimeout = "10s", requestTimeout = "10s", connectionIdleTimeout = null) + } + val properties = snapshotProperties(allServicesDependenciesIdentifier = "*", connectionIdleTimeout = "5s") + // when + + val outgoing = proto.toOutgoing(properties) // expects - val exception = assertThrows { - proto.structValue?.fieldsMap?.get(filter).toComparisonFilter() - } - assertThat(exception.status.description) - .isEqualTo(errorMessage) - assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(outgoing.allServicesDependencies).isTrue() + assertThat(outgoing.defaultServiceSettings).hasTimeouts( + idleTimeout = "10s", + requestTimeout = "10s", + connectionIdleTimeout = "5s" + ) + assertThat(outgoing.getServiceDependencies()).isEmpty() } - @ParameterizedTest - @MethodSource("validComparisonFilterData") - fun `should set duration filter for accessLogFilter`(input: String, op: ComparisonFilter.Op, code: Int) { + @Test + fun `should parse allServiceDependency and use timeouts from properties`() { // given - val proto = accessLogFilterProto(value = input, fieldName = "duration_filter") - + val proto = outgoingDependenciesProto { + withService(serviceName = "*", idleTimeout = null, requestTimeout = null) + } + val properties = + snapshotProperties( + allServicesDependenciesIdentifier = "*", + idleTimeout = "5s", + requestTimeout = "5s", + connectionIdleTimeout = "5s" + ) // when - val durationFilterSettings = proto.structValue?.fieldsMap?.get("duration_filter").toComparisonFilter() + + val outgoing = proto.toOutgoing(properties) // expects - assertThat(durationFilterSettings?.comparisonCode).isEqualTo(code) - assertThat(durationFilterSettings?.comparisonOperator).isEqualTo(op) + assertThat(outgoing.allServicesDependencies).isTrue() + assertThat(outgoing.defaultServiceSettings).hasTimeouts( + idleTimeout = "5s", + requestTimeout = "5s", + connectionIdleTimeout = "5s" + ) + assertThat(outgoing.getServiceDependencies()).isEmpty() } - @ParameterizedTest - @MethodSource("invalidComparisonFilterData") - fun `should throw exception for invalid duration filter data`(input: String) { + @Test + fun `should throw exception when there are multiple allServiceDependency`() { // given - val proto = accessLogFilterProto(value = input, fieldName = "duration_filter") + val proto = outgoingDependenciesProto { + withServices(serviceDependencies = listOf("*", "*", "a")) + } // expects val exception = assertThrows { - proto.structValue?.fieldsMap?.get("duration_filter").toComparisonFilter() + proto.toOutgoing(snapshotProperties(allServicesDependenciesIdentifier = "*")) } assertThat(exception.status.description) - .isEqualTo("Invalid access log comparison filter. Expected OPERATOR:VALUE") + .isEqualTo("Define at most one 'all serviceDependencies identifier' as an service dependency") assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) } @Test - fun `should throw exception for null value header filter data`() { + fun `should reject domain dependency with unsupported all services dependencies identifier`() { // given - val proto = accessLogFilterProto(value = null, fieldName = "header_filter") + val proto = outgoingDependenciesProto { + withDomain(url = "*") + } + val properties = snapshotProperties(allServicesDependenciesIdentifier = "*") // expects - val exception = assertThrows { - proto.structValue?.fieldsMap?.get("header_filter").toHeaderFilter() - } + val exception = assertThrows { proto.toOutgoing(properties) } assertThat(exception.status.description) - .isEqualTo("Invalid access log header filter. Expected HEADER_NAME:REGEX") + .isEqualTo("Unsupported 'all serviceDependencies identifier' for domain dependency: *") assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) } +} + +class DomainPatternDependencyTest { + @Test - fun `should set header filter for accessLogFilter`() { + fun `should accept domain pattern dependency`() { // given - val proto = accessLogFilterProto(value = "test:^((.+):(.+))$", fieldName = "header_filter") + val proto = outgoingDependenciesProto { + withDomainPattern(pattern = "*.example.com") + } // when - val headerFilterSettings = proto.structValue?.fieldsMap?.get("header_filter").toHeaderFilter() + val outgoing = proto.toOutgoing(snapshotProperties()) // expects - assertThat(headerFilterSettings?.headerName).isEqualTo("test") - assertThat(headerFilterSettings?.regex).isEqualTo("^((.+):(.+))\$") + val dependency = outgoing.getDomainPatternDependencies().single() + assertThat(dependency.domainPattern).isEqualTo("*.example.com") } @Test - fun `should throw exception for invalid header filter data`() { + fun `should reject domain pattern dependency with schema`() { // given - val proto = accessLogFilterProto(value = "test;test", fieldName = "header_filter") + val proto = outgoingDependenciesProto { + withDomainPattern(pattern = "http://example.com") + } // expects - val exception = assertThrows { - proto.structValue?.fieldsMap?.get("header_filter").toHeaderFilter() - } - assertThat(exception.status.description) - .isEqualTo("Invalid access log header filter. Expected HEADER_NAME:REGEX") + val exception = assertThrows { proto.toOutgoing(snapshotProperties()) } + assertThat(exception.status.description).isEqualTo( + "Unsupported format for domainPattern: domainPattern cannot contain a schema like http:// or https://" + ) assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) } +} + +class TagDependencyTest { @Test - fun `should set response flag filter for accessLogFilter`() { + fun `should deduplicate tag dependencies based on tag`() { // given - val availableFlags = listOf( - "UH", "UF", "UO", "NR", "URX", "NC", "DT", "DC", "LH", "UT", "LR", "UR", - "UC", "DI", "FI", "RL", "UAEX", "RLSE", "IH", "SI", "DPE", "UPE", "UMSDR", - "OM", "DF" - ) - val proto = accessLogFilterProto(value = availableFlags.joinToString(","), fieldName = "response_flag_filter") + val proto = outgoingDependenciesProto { + withTag(tag = "tag-1", requestTimeout = "8s", idleTimeout = "8s") + withTag( + tag = "tag-1", + requestTimeout = "10s", + idleTimeout = "10s", + connectionIdleTimeout = "10s" + ) + withTag(tag = "tag-2") + } // when - val responseFlags = proto.structValue?.fieldsMap?.get("response_flag_filter").toResponseFlagFilter() + val outgoing = proto.toOutgoing(snapshotProperties()) - // expects - assertThat(responseFlags).isEqualTo(availableFlags) - } + // expect + val dependencies = outgoing.getTagDependencies() + assertThat(dependencies) + .hasSize(2) - @Test - fun `should throw exception for invalid response flag filter data`() { - // given - val availableFlagsAndInvalid = listOf( - "UH", "UF", "UO", "NR", "URX", "NC", "DT", "DC", "LH", "UT", "LR", "UR", - "UC", "DI", "FI", "RL", "UAEX", "RLSE", "IH", "SI", "DPE", "UPE", "UMSDR", - "OM", "DF", "invalid" + assertThat(dependencies[0].tag).isEqualTo("tag-1") + assertThat(dependencies[0].settings).hasTimeouts( + idleTimeout = "10s", + requestTimeout = "10s", + connectionIdleTimeout = "10s" ) - val proto = - accessLogFilterProto(value = availableFlagsAndInvalid.joinToString(","), fieldName = "response_flag_filter") - - // expects - val exception = assertThrows { - proto.structValue?.fieldsMap?.get("response_flag_filter").toResponseFlagFilter() - } - assertThat(exception.status.description) - .isEqualTo("Invalid access log response flag filter. Expected valid values separated by comma") - assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(dependencies[1].tag).isEqualTo("tag-2") } @Test - fun `should set not health check filter for accessLogFilter`() { + fun `should parse tag dependencies and for missing config use config defined in allServiceDependency`() { // given - val proto = accessLogBooleanFilterProto(value = true, fieldName = "not_health_check_filter") - + val proto = outgoingDependenciesProto { + withService(serviceName = "*", idleTimeout = "10s", requestTimeout = "10s", connectionIdleTimeout = "10s") + withTag( + tag = "tag-1", + idleTimeout = "5s", + requestTimeout = null, + connectionIdleTimeout = null + ) + withTag( + tag = "tag-2", + idleTimeout = null, + requestTimeout = "4s", + connectionIdleTimeout = null + ) + withTag( + tag = "tag-3", + idleTimeout = null, + requestTimeout = null, + connectionIdleTimeout = "3s" + ) + withTag( + tag = "tag-4", + idleTimeout = null, + requestTimeout = null, + connectionIdleTimeout = null + ) + } + val properties = snapshotProperties(allServicesDependenciesIdentifier = "*") // when - val value = proto.structValue?.fieldsMap?.get("not_health_check_filter")?.boolValue + + val outgoing = proto.toOutgoing(properties) // expects - assertThat(value).isEqualTo(true) + assertThat(outgoing.allServicesDependencies).isTrue() + assertThat(outgoing.defaultServiceSettings).hasTimeouts( + idleTimeout = "10s", + requestTimeout = "10s", + connectionIdleTimeout = "10s" + ) + + outgoing.getTagDependencies().assertTagDependency("tag-1") + .hasTimeouts(idleTimeout = "5s", requestTimeout = "10s", connectionIdleTimeout = "10s") + outgoing.getTagDependencies().assertTagDependency("tag-2") + .hasTimeouts(idleTimeout = "10s", requestTimeout = "4s", connectionIdleTimeout = "10s") + outgoing.getTagDependencies().assertTagDependency("tag-3") + .hasTimeouts(idleTimeout = "10s", requestTimeout = "10s", connectionIdleTimeout = "3s") + outgoing.getTagDependencies().assertTagDependency("tag-4") + .hasTimeouts(idleTimeout = "10s", requestTimeout = "10s", connectionIdleTimeout = "10s") } @Test - fun `should throw exception for null value response flag filter data`() { + fun `should parse tag dependencies and for missing configs use config defined in properties when allServiceDependency isn't defined`() { // given - val proto = accessLogFilterProto(value = null, fieldName = "response_flag_filter") - - // expects - val exception = assertThrows { - proto.structValue?.fieldsMap?.get("response_flag_filter").toResponseFlagFilter() + val proto = outgoingDependenciesProto { + withTag( + tag = "tag-1", + idleTimeout = "5s", + requestTimeout = null, + connectionIdleTimeout = null + ) + withTag( + tag = "tag-2", + idleTimeout = null, + requestTimeout = "4s", + connectionIdleTimeout = null + ) + withTag( + tag = "tag-3", + idleTimeout = null, + requestTimeout = null, + connectionIdleTimeout = "3s" + ) + withTag( + tag = "tag-4", + idleTimeout = null, + requestTimeout = null, + connectionIdleTimeout = null + ) } - assertThat(exception.status.description) - .isEqualTo("Invalid access log response flag filter. Expected valid values separated by comma") - assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) - } + val properties = snapshotProperties(idleTimeout = "12s", requestTimeout = "12s", connectionIdleTimeout = "12s") + // when - fun ObjectAssert.hasTimeouts( - idleTimeout: String, - connectionIdleTimeout: String, - requestTimeout: String - ): ObjectAssert { - this.extracting { it.timeoutPolicy }.isEqualTo( - Outgoing.TimeoutPolicy( - idleTimeout = Durations.parse(idleTimeout), - connectionIdleTimeout = Durations.parse(connectionIdleTimeout), - requestTimeout = Durations.parse(requestTimeout) - ) + val outgoing = proto.toOutgoing(properties) + + // expects + assertThat(outgoing.allServicesDependencies).isFalse() + assertThat(outgoing.defaultServiceSettings).hasTimeouts( + idleTimeout = "12s", + requestTimeout = "12s", + connectionIdleTimeout = "12s" ) - return this - } - fun List.assertServiceDependency(name: String): ObjectAssert { - val list = this.filter { it.service == name } - assertThat(list).hasSize(1) - val single = list.single().settings - return assertThat(single) + outgoing.getTagDependencies().assertTagDependency("tag-1") + .hasTimeouts(idleTimeout = "5s", requestTimeout = "12s", connectionIdleTimeout = "12s") + outgoing.getTagDependencies().assertTagDependency("tag-2") + .hasTimeouts(idleTimeout = "12s", requestTimeout = "4s", connectionIdleTimeout = "12s") + outgoing.getTagDependencies().assertTagDependency("tag-3") + .hasTimeouts(idleTimeout = "12s", requestTimeout = "12s", connectionIdleTimeout = "3s") + outgoing.getTagDependencies().assertTagDependency("tag-4") + .hasTimeouts(idleTimeout = "12s", requestTimeout = "12s", connectionIdleTimeout = "12s") } - fun List.assertDomainDependency(name: String): ObjectAssert { - val list = this.filter { it.domain == name } + fun List.assertTagDependency(name: String): ObjectAssert { + val list = this.filter { it.tag == name } assertThat(list).hasSize(1) val single = list.single().settings return assertThat(single) } +} - private fun createJwtSnapshotProperties(): SnapshotProperties { - val snapshotProperties = SnapshotProperties() - val jwtFilterProperties = JwtFilterProperties() - val oauthProviders = mapOf( - "oauth2-mock" to - OAuthProvider( - jwksUri = URI.create("http://localhost:8080/jwks-address/"), - clusterName = "oauth" - ) - ) - jwtFilterProperties.providers = oauthProviders - snapshotProperties.jwt = jwtFilterProperties +private fun snapshotProperties( + allServicesDependenciesIdentifier: String = "*", + handleInternalRedirect: Boolean = false, + idleTimeout: String = "120s", + connectionIdleTimeout: String = "120s", + requestTimeout: String = "120s" +) = SnapshotProperties().apply { + outgoingPermissions.allServicesDependencies.identifier = allServicesDependenciesIdentifier + egress.handleInternalRedirect = handleInternalRedirect + egress.commonHttp.idleTimeout = Duration.ofNanos(Durations.toNanos(Durations.parse(idleTimeout))) + egress.commonHttp.connectionIdleTimeout = + Duration.ofNanos(Durations.toNanos(Durations.parse(connectionIdleTimeout))) + egress.commonHttp.requestTimeout = Duration.ofNanos(Durations.toNanos(Durations.parse(requestTimeout))) +} - return snapshotProperties - } +fun timeouts( + idleTimeout: String, + connectionIdleTimeout: String, + requestTimeout: String +) = DependencySettings( + timeoutPolicy = Outgoing.TimeoutPolicy( + idleTimeout = Durations.parse(idleTimeout), + connectionIdleTimeout = Durations.parse(connectionIdleTimeout), + requestTimeout = Durations.parse(requestTimeout) + ) +) + +fun ObjectAssert.hasTimeouts( + idleTimeout: String, + connectionIdleTimeout: String, + requestTimeout: String +): ObjectAssert { + this.extracting { it.timeoutPolicy }.isEqualTo( + Outgoing.TimeoutPolicy( + idleTimeout = Durations.parse(idleTimeout), + connectionIdleTimeout = Durations.parse(connectionIdleTimeout), + requestTimeout = Durations.parse(requestTimeout) + ) + ) + return this } diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataValidatorTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataValidatorTest.kt index 76451d45c..b660796cb 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataValidatorTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataValidatorTest.kt @@ -1,5 +1,6 @@ package pl.allegro.tech.servicemesh.envoycontrol.groups +import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest import io.grpc.Status import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType @@ -16,10 +17,11 @@ import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest as DiscoveryReq class NodeMetadataValidatorTest { - val validator = NodeMetadataValidator(SnapshotProperties().apply { + private val validator = NodeMetadataValidator(SnapshotProperties().apply { outgoingPermissions = createOutgoingPermissions( enabled = true, - servicesAllowedToUseWildcard = mutableSetOf("vis-1", "vis-2") + servicesAllowedToUseWildcard = mutableSetOf("vis-1", "vis-2"), + tagPrefix = "tag:" ) incomingPermissions = createIncomingPermissions( enabled = true, @@ -37,16 +39,12 @@ class NodeMetadataValidatorTest { ) val request = DiscoveryRequestV3.newBuilder().setNode(node).build() - // when - val exception = catchThrowable { validator.onV3StreamRequest(streamId = 123, request = request) } - // then - assertThat(exception).isInstanceOf(WildcardPrincipalValidationException::class.java) - val validationException = exception as WildcardPrincipalValidationException - assertThat(validationException.status.description) - .isEqualTo("Blocked service regular-1 from allowing everyone in incoming permissions. Only defined services can use that.") - assertThat(validationException.status.code) - .isEqualTo(Status.Code.INVALID_ARGUMENT) + validator.assertThrow( + request, + WildcardPrincipalValidationException::class.java, + "Blocked service regular-1 from allowing everyone in incoming permissions. Only defined services can use that." + ) } @Test @@ -59,16 +57,12 @@ class NodeMetadataValidatorTest { ) val request = DiscoveryRequestV3.newBuilder().setNode(node).build() - // when - val exception = catchThrowable { validator.onV3StreamRequest(streamId = 123, request = request) } - // expects - assertThat(exception).isInstanceOf(WildcardPrincipalMixedWithOthersValidationException::class.java) - val validationException = exception as WildcardPrincipalMixedWithOthersValidationException - assertThat(validationException.status.description) - .isEqualTo("Blocked service vis-1 from allowing everyone in incoming permissions. Either a wildcard or a list of clients must be provided.") - assertThat(validationException.status.code) - .isEqualTo(Status.Code.INVALID_ARGUMENT) + validator.assertThrow( + request, + WildcardPrincipalMixedWithOthersValidationException::class.java, + "Blocked service vis-1 from allowing everyone in incoming permissions. Either a wildcard or a list of clients must be provided." + ) } @Test @@ -80,16 +74,12 @@ class NodeMetadataValidatorTest { ) val request = DiscoveryRequestV3.newBuilder().setNode(node).build() - // when - val exception = catchThrowable { validator.onV3StreamRequest(streamId = 123, request = request) } - // expects - assertThat(exception).isInstanceOf(AllDependenciesValidationException::class.java) - val validationException = exception as AllDependenciesValidationException - assertThat(validationException.status.description) - .isEqualTo("Blocked service regular-1 from using all dependencies. Only defined services can use all dependencies") - assertThat(validationException.status.code) - .isEqualTo(Status.Code.INVALID_ARGUMENT) + validator.assertThrow( + request, + AllDependenciesValidationException::class.java, + "Blocked service regular-1 from using all dependencies. Only defined services can use all dependencies" + ) } @Test @@ -104,7 +94,7 @@ class NodeMetadataValidatorTest { val request = DiscoveryRequestV3.newBuilder().setNode(node).build() // then - assertDoesNotThrow { validator.onV3StreamRequest(123, request = request) } + validator.notThrow(request) } @Test @@ -118,7 +108,7 @@ class NodeMetadataValidatorTest { val request = DiscoveryRequestV3.newBuilder().setNode(node).build() // then - assertDoesNotThrow { validator.onV3StreamRequest(123, request = request) } + validator.notThrow(request) } @Test @@ -137,7 +127,7 @@ class NodeMetadataValidatorTest { val request = DiscoveryRequestV3.newBuilder().setNode(node).build() // then - assertDoesNotThrow { permissionsDisabledValidator.onV3StreamRequest(123, request = request) } + permissionsDisabledValidator.notThrow(request) } @ParameterizedTest @@ -165,17 +155,12 @@ class NodeMetadataValidatorTest { ) val request = DiscoveryRequestV3.newBuilder().setNode(node).build() - // when - val exception = - catchThrowable { configurationModeValidator.onV3StreamRequest(streamId = 123, request = request) } - // expects - assertThat(exception).isInstanceOf(ConfigurationModeNotSupportedException::class.java) - val validationException = exception as ConfigurationModeNotSupportedException - assertThat(validationException.status.description) - .isEqualTo("Blocked service regular-1 from receiving updates. $modeNotSupportedName is not supported by server.") - assertThat(validationException.status.code) - .isEqualTo(Status.Code.INVALID_ARGUMENT) + configurationModeValidator.assertThrow( + request, + ConfigurationModeNotSupportedException::class.java, + "Blocked service regular-1 from receiving updates. $modeNotSupportedName is not supported by server." + ) } @ParameterizedTest @@ -203,7 +188,7 @@ class NodeMetadataValidatorTest { val request = DiscoveryRequestV3.newBuilder().setNode(node).build() // then - assertDoesNotThrow { configurationModeValidator.onV3StreamRequest(123, request = request) } + validator.notThrow(request) } @Test @@ -219,14 +204,11 @@ class NodeMetadataValidatorTest { val request = DiscoveryRequestV3.newBuilder().setNode(node).build() // expects - assertThatExceptionOfType(ServiceNameNotProvidedException::class.java) - .isThrownBy { requireServiceNameValidator.onV3StreamRequest(streamId = 123, request = request) } - .satisfies { - assertThat(it.status.description).isEqualTo( - "Service name has not been provided." - ) - assertThat(it.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) - } + requireServiceNameValidator.assertThrow( + request, + ServiceNameNotProvidedException::class.java, + "Service name has not been provided." + ) } @Test @@ -239,7 +221,7 @@ class NodeMetadataValidatorTest { val request = DiscoveryRequestV3.newBuilder().setNode(node).build() // then - assertDoesNotThrow { validator.onV3StreamRequest(123, request = request) } + validator.notThrow(request) } @Test @@ -254,40 +236,85 @@ class NodeMetadataValidatorTest { .build() // then - assertThatExceptionOfType(RateLimitIncorrectValidationException::class.java) - .isThrownBy { validator.onV3StreamRequest(123, request = request) } - .satisfies { - assertThat(it.status.description).isEqualTo( - "Rate limit value: 0/j is incorrect." - ) - assertThat(it.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) - } + validator.assertThrow( + request, + RateLimitIncorrectValidationException::class.java, + "Rate limit value: 0/j is incorrect." + ) + } + + @Test + fun `should throw an exception when service is using tag dependency without prefix`() { + // given + val node = nodeV3( + serviceName = "tag-service", + tagDependencies = setOf("tag:xyz", "abc") + ) + val request = DiscoveryRequestV3.newBuilder() + .setNode(node) + .build() + + // then + validator.assertThrow( + request, + TagDependencyValidationException::class.java, + "Blocked service tag-service from using tag dependencies [abc]. Only allowed tags are supported." + ) + } + + @Test + fun `should not throw an exception when service is using tag dependency with prefix`() { + // given + val node = nodeV3( + serviceName = "tag-service", + tagDependencies = setOf("tag:xyz", "tag:abc") + ) + val request = DiscoveryRequestV3.newBuilder() + .setNode(node) + .build() + + // then + validator.notThrow(request) } private fun createIncomingPermissions( enabled: Boolean = false, servicesAllowedToUseWildcard: MutableSet = mutableSetOf() - ): IncomingPermissionsProperties { - val incomingPermissions = IncomingPermissionsProperties() - incomingPermissions.enabled = enabled - incomingPermissions.tlsAuthentication.servicesAllowedToUseWildcard = servicesAllowedToUseWildcard - return incomingPermissions + ): IncomingPermissionsProperties = IncomingPermissionsProperties().apply { + this.enabled = enabled + this.tlsAuthentication.servicesAllowedToUseWildcard = servicesAllowedToUseWildcard } private fun createOutgoingPermissions( enabled: Boolean = false, - servicesAllowedToUseWildcard: MutableSet = mutableSetOf() - ): OutgoingPermissionsProperties { - val outgoingPermissions = OutgoingPermissionsProperties() - outgoingPermissions.enabled = enabled - outgoingPermissions.servicesAllowedToUseWildcard = servicesAllowedToUseWildcard - return outgoingPermissions + servicesAllowedToUseWildcard: MutableSet = mutableSetOf(), + tagPrefix: String = "" + ): OutgoingPermissionsProperties = OutgoingPermissionsProperties().apply { + this.enabled = enabled + this.servicesAllowedToUseWildcard = servicesAllowedToUseWildcard + this.tagPrefix = tagPrefix } - private fun createCommunicationMode(ads: Boolean = true, xds: Boolean = true): EnabledCommunicationModes { - val enabledCommunicationModes = EnabledCommunicationModes() - enabledCommunicationModes.ads = ads - enabledCommunicationModes.xds = xds - return enabledCommunicationModes + private fun createCommunicationMode( + ads: Boolean = true, + xds: Boolean = true + ): EnabledCommunicationModes = EnabledCommunicationModes().apply { + this.ads = ads + this.xds = xds + } + + + private fun NodeMetadataValidator.assertThrow( + request: DiscoveryRequest, + exceptionClass: Class, + description: String, + ) = assertThatExceptionOfType(exceptionClass) + .isThrownBy { this.onV3StreamRequest(123, request) } + .matches { it.status.description == description } + .matches { it.status.code == Status.Code.INVALID_ARGUMENT } + + + private fun NodeMetadataValidator.notThrow(request: DiscoveryRequest) = assertDoesNotThrow { + this.onV3StreamRequest(123, request) } } diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/TestNodeFactory.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/TestNodeFactory.kt index 59757cb6d..a205957dd 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/TestNodeFactory.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/TestNodeFactory.kt @@ -21,7 +21,8 @@ fun nodeV3( connectionIdleTimeout: String? = null, healthCheckPath: String? = null, healthCheckClusterName: String? = null, - rateLimit: String? = null + rateLimit: String? = null, + tagDependencies: Set = emptySet() ): NodeV3 { val meta = NodeV3.newBuilder().metadataBuilder @@ -37,13 +38,14 @@ fun nodeV3( meta.putFields("ads", Value.newBuilder().setBoolValue(ads).build()) } - if (incomingSettings || serviceDependencies.isNotEmpty()) { + if (incomingSettings || serviceDependencies.isNotEmpty() || tagDependencies.isNotEmpty()) { meta.putFields( "proxy_settings", proxySettingsProto( path = "/endpoint", clients = clients, serviceDependencies = serviceDependencies, + tagDependencies = tagDependencies, incomingSettings = incomingSettings, idleTimeout = idleTimeout, responseTimeout = responseTimeout, @@ -129,7 +131,8 @@ fun proxySettingsProto( healthCheckPath: String? = null, healthCheckClusterName: String? = null, clients: List = listOf("client1"), - rateLimit: String? = null + rateLimit: String? = null, + tagDependencies: Set = emptySet() ): Value = struct { if (incomingSettings) { putFields("incoming", struct { @@ -162,9 +165,14 @@ fun proxySettingsProto( }) }) } - if (serviceDependencies.isNotEmpty()) { + if (serviceDependencies.isNotEmpty() || tagDependencies.isNotEmpty()) { putFields("outgoing", outgoingDependenciesProto { - withServices(serviceDependencies.toList(), idleTimeout, responseTimeout) + serviceDependencies.forEach { + withService(it, idleTimeout, responseTimeout) + } + tagDependencies.forEach { + withTag(it, idleTimeout, responseTimeout) + } }) } } @@ -195,6 +203,7 @@ class OutgoingDependenciesProtoScope { val service: String? = null, val domain: String? = null, val domainPattern: String? = null, + val tag: String? = null, val idleTimeout: String? = null, val connectionIdleTimeout: String? = null, val requestTimeout: String? = null, @@ -256,6 +265,20 @@ class OutgoingDependenciesProtoScope { ) ) + fun withTag( + tag: String, + idleTimeout: String? = null, + connectionIdleTimeout: String? = null, + requestTimeout: String? = null + ) = dependencies.add( + Dependency( + tag = tag, + idleTimeout = idleTimeout, + connectionIdleTimeout = connectionIdleTimeout, + requestTimeout = requestTimeout + ) + ) + fun withInvalid(service: String? = null, domain: String? = null) = dependencies.add( Dependency( service = service, @@ -276,6 +299,7 @@ fun outgoingDependenciesProto( service = it.service, domain = it.domain, domainPattern = it.domainPattern, + tag = it.tag, idleTimeout = it.idleTimeout, connectionIdleTimeout = it.connectionIdleTimeout, requestTimeout = it.requestTimeout, @@ -292,17 +316,19 @@ fun outgoingDependencyProto( service: String? = null, domain: String? = null, domainPattern: String? = null, + tag: String? = null, handleInternalRedirect: Boolean? = null, idleTimeout: String? = null, connectionIdleTimeout: String? = null, requestTimeout: String? = null, retryPolicy: RetryPolicyInput? = null ) = struct { - service?.also { putFields("service", string(service)) } - domain?.also { putFields("domain", string(domain)) } - retryPolicy?.also { putFields("retryPolicy", retryPolicyProto(retryPolicy)) } - domainPattern?.also { putFields("domainPattern", string(domainPattern)) } - handleInternalRedirect?.also { putFields("handleInternalRedirect", boolean(handleInternalRedirect)) } + service?.also { putFields("service", string(it)) } + domain?.also { putFields("domain", string(it)) } + domainPattern?.also { putFields("domainPattern", string(it)) } + tag?.also { putFields("tag", string(it)) } + retryPolicy?.also { putFields("retryPolicy", retryPolicyProto(it)) } + handleInternalRedirect?.also { putFields("handleInternalRedirect", boolean(it)) } if (idleTimeout != null || requestTimeout != null || connectionIdleTimeout != null) { putFields("timeoutPolicy", outgoingTimeoutPolicy(idleTimeout, connectionIdleTimeout, requestTimeout)) } diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt index a185b02c1..53e5c68e8 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt @@ -1,4 +1,83 @@ package pl.allegro.tech.servicemesh.envoycontrol.snapshot -class EnvoySnapshotFactory { +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import pl.allegro.tech.servicemesh.envoycontrol.services.ClusterState +import pl.allegro.tech.servicemesh.envoycontrol.services.Locality +import pl.allegro.tech.servicemesh.envoycontrol.services.MultiClusterState +import pl.allegro.tech.servicemesh.envoycontrol.services.ServiceInstance +import pl.allegro.tech.servicemesh.envoycontrol.services.ServiceInstances +import pl.allegro.tech.servicemesh.envoycontrol.services.ServiceName +import pl.allegro.tech.servicemesh.envoycontrol.services.ServicesState +import java.util.concurrent.ConcurrentHashMap + +class EnvoySnapshotFactoryTest { + + @Test + fun `should return all tags when prefix is empty`() { + // given + val tagPrefix = "" + val serviceTags = mapOf("abc" to setOf("uws", "poc"), "xyz" to setOf("uj"), "qwerty" to setOf()) + val state: MultiClusterState = MultiClusterState(listOf( + ClusterState(serviceState(serviceTags), Locality.LOCAL, "cluster") + )) + + // when + val tags = EnvoySnapshotFactory.tagExtractor(tagPrefix, state) + + // then + assertThat(tags).isEqualTo(serviceTags) + } + + @Test + fun `should return all tags with prefix`() { + val tagPrefix = "tag:" + val serviceTags = mapOf("abc" to setOf("tag:uws", "poc"), "xyz" to setOf("uj"), "qwerty" to setOf()) + val state: MultiClusterState = MultiClusterState(listOf( + ClusterState(serviceState(serviceTags), Locality.LOCAL, "cluster") + )) + + // when + val tags = EnvoySnapshotFactory.tagExtractor(tagPrefix, state) + + // then + assertThat(tags).isEqualTo(mapOf( + "abc" to setOf("tag:uws"), + "xyz" to emptySet(), + "qwerty" to emptySet() + )) + } + + @Test + fun `should merge multiple Cluster State`() { + // given + val tagPrefix = "" + val serviceTagsCluster1 = mapOf("abc" to setOf("uws", "poc"), "xyz" to setOf("uj"), "qwerty" to setOf()) + val serviceTagsCluster2 = mapOf("abc" to setOf("lkj"), "xyz" to setOf(), "qwerty" to setOf("ban")) + val state: MultiClusterState = MultiClusterState(listOf( + ClusterState(serviceState(serviceTagsCluster1), Locality.LOCAL, "cluster"), + ClusterState(serviceState(serviceTagsCluster2), Locality.LOCAL, "cluster2") + )) + + // when + val tags = EnvoySnapshotFactory.tagExtractor(tagPrefix, state) + + // then + assertThat(tags).isEqualTo(mapOf( + "abc" to setOf("uws", "poc", "lkj"), + "xyz" to setOf("uj"), + "qwerty" to setOf("ban") + )) + } + + private fun serviceState(servicesTags: Map>): ServicesState { + val map: ConcurrentHashMap = ConcurrentHashMap() + servicesTags.forEach { + val instances = listOf(1, 2, 3).map { id -> + ServiceInstance("${it.key}-$id", it.value, null, null) + }.toSet() + map[it.key] = ServiceInstances(it.key, instances) + } + return ServicesState(map) + } } diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotUpdaterTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotUpdaterTest.kt index 85f5105a2..3ffe4fa87 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotUpdaterTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotUpdaterTest.kt @@ -1319,6 +1319,7 @@ class SnapshotUpdaterTest { snapshotProperties, EnvoyHttpFilters.emptyFilters ), + routeSpecificationFactory = RouteSpecificationFactory(snapshotProperties), // Remember when LDS change we have to send RDS again snapshotsVersions = SnapshotsVersions(), properties = snapshotProperties, diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/ResourceUtils.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/ResourceUtils.kt index 3529d3845..410f3b1b7 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/ResourceUtils.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/ResourceUtils.kt @@ -1,2 +1,61 @@ package pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource +import com.google.protobuf.Duration +import com.google.protobuf.util.Durations +import io.envoyproxy.envoy.config.cluster.v3.Cluster +import io.envoyproxy.envoy.config.core.v3.AggregatedConfigSource +import io.envoyproxy.envoy.config.core.v3.ConfigSource +import io.envoyproxy.envoy.config.core.v3.HttpProtocolOptions +import pl.allegro.tech.servicemesh.envoycontrol.groups.DependencySettings +import pl.allegro.tech.servicemesh.envoycontrol.groups.Outgoing +import pl.allegro.tech.servicemesh.envoycontrol.groups.ServiceDependency +import pl.allegro.tech.servicemesh.envoycontrol.groups.TagDependency +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotProperties +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.clusters.EnvoyClusterFactoryTest + +fun createClusters( + properties: SnapshotProperties, + serviceNames: List +): Map { + return serviceNames.map { + createCluster(properties, it) + }.associateBy { it.name } +} + +fun createCluster( + defaultProperties: SnapshotProperties, + serviceName: String, +): Cluster { + return Cluster.newBuilder().setName(serviceName) + .setType(Cluster.DiscoveryType.EDS) + .setConnectTimeout(Durations.fromMillis(defaultProperties.edsConnectionTimeout.toMillis())) + .setEdsClusterConfig( + Cluster.EdsClusterConfig.newBuilder().setEdsConfig( + ConfigSource.newBuilder().setAds(AggregatedConfigSource.newBuilder()) + ).setServiceName(serviceName) + ) + .setLbPolicy(defaultProperties.loadBalancing.policy) + .setCommonHttpProtocolOptions( + HttpProtocolOptions.newBuilder() + .setIdleTimeout(Duration.newBuilder().setSeconds(100).build()) + ) + .build() +} + +fun serviceDependency(name: String, idleTimeout: Long) = ServiceDependency( + name, + settings = DependencySettings( + timeoutPolicy = Outgoing.TimeoutPolicy( + connectionIdleTimeout = Duration.newBuilder().setSeconds(idleTimeout).build() + ) + ) +) + +fun tagDependency(name: String, idleTimeout: Long) = TagDependency( + name, + settings = DependencySettings( + timeoutPolicy = Outgoing.TimeoutPolicy( + connectionIdleTimeout = Duration.newBuilder().setSeconds(idleTimeout).build() + ) + ) +) diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt index 479f1ef8d..e270bd788 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt @@ -1,2 +1,295 @@ -package pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.clusters;public class EnvoyClusterFactoryTest { +package pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.clusters; + +import io.envoyproxy.envoy.config.cluster.v3.Cluster +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.ObjectAssert +import org.junit.jupiter.api.Test +import pl.allegro.tech.servicemesh.envoycontrol.groups.AllServicesGroup +import pl.allegro.tech.servicemesh.envoycontrol.groups.CommunicationMode +import pl.allegro.tech.servicemesh.envoycontrol.groups.Outgoing +import pl.allegro.tech.servicemesh.envoycontrol.groups.ProxySettings +import pl.allegro.tech.servicemesh.envoycontrol.groups.ServicesGroup +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.GlobalSnapshot +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotProperties +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.createClusters +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.serviceDependency +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.tagDependency + +class EnvoyClusterFactoryTest { + + companion object { + val allServicesGroup = AllServicesGroup( + communicationMode = CommunicationMode.ADS, + serviceName = "service-name", + discoveryServiceName = "service-name" + ) + + val serviceGroup = ServicesGroup( + communicationMode = CommunicationMode.ADS, + serviceName = "service-name", + discoveryServiceName = "service-name" + ) + } + + @Test + fun `should return cluster from service dependency`() { + // given + val properties = SnapshotProperties() + val factory = EnvoyClustersFactory(properties) + val group = serviceGroup.copy( + proxySettings = ProxySettings( + outgoing = Outgoing( + serviceDependencies = listOf(serviceDependency("service-A", 33)) + ) + ) + ) + val services = listOf("service-A", "service-B", "service-C") + val globalSnapshot = GlobalSnapshot( + clusters = createClusters(properties, services), + allServicesNames = services.toSet(), + endpoints = emptyMap(), + clusterConfigurations = emptyMap(), + securedClusters = emptyMap(), + tags = emptyMap() + ) + + // when + val clustersForGroup = factory.getClustersForGroup(group, globalSnapshot) + + // then + assertThat(clustersForGroup) + .hasSize(1) + .first() + .matches { it.name == "service-A" } + .hasIdleTimeout(33) + } + + @Test + fun `should return clusters from tag dependency`() { + // given + val properties = SnapshotProperties() + val factory = EnvoyClustersFactory(properties) + val group = serviceGroup.copy( + proxySettings = ProxySettings( + outgoing = Outgoing( + tagDependencies = listOf(tagDependency("tag", 33)) + ) + ) + ) + val services = listOf("service-A", "service-B", "service-C") + val globalSnapshot = GlobalSnapshot( + clusters = createClusters(properties, services), + allServicesNames = services.toSet(), + endpoints = emptyMap(), + clusterConfigurations = emptyMap(), + securedClusters = emptyMap(), + tags = mapOf("service-A" to setOf("tag"), "service-C" to setOf("tag")) + ) + + // when + val clustersForGroup = factory.getClustersForGroup(group, globalSnapshot) + + // then + assertThat(clustersForGroup) + .hasSize(2) + .extracting { it.name } + .containsAll(listOf("service-A", "service-C")) + clustersForGroup.assertServiceCluster("service-A") + .hasIdleTimeout(33) + clustersForGroup.assertServiceCluster("service-C") + .hasIdleTimeout(33) + } + + @Test + fun `should return clusters from tag dependency with `() { + // given + val properties = SnapshotProperties() + val factory = EnvoyClustersFactory(properties) + val group = serviceGroup.copy( + proxySettings = ProxySettings( + outgoing = Outgoing( + tagDependencies = listOf( + tagDependency("tag-1", 33), + tagDependency("tag-2", 27)) + ) + ) + ) + val services = listOf("service-A", "service-B", "service-C") + val globalSnapshot = GlobalSnapshot( + clusters = createClusters(properties, services), + allServicesNames = services.toSet(), + endpoints = emptyMap(), + clusterConfigurations = emptyMap(), + securedClusters = emptyMap(), + tags = mapOf("service-A" to setOf("tag-1"), "service-C" to setOf("tag-1", "tag-2"), "service-B" to setOf("tag-2")) + ) + + // when + val clustersForGroup = factory.getClustersForGroup(group, globalSnapshot) + + // then + assertThat(clustersForGroup) + .hasSize(3) + .extracting { it.name } + .containsAll(services) + + clustersForGroup.assertServiceCluster("service-A") + .hasIdleTimeout(33) + clustersForGroup.assertServiceCluster("service-B") + .hasIdleTimeout(27) + clustersForGroup.assertServiceCluster("service-C") + .hasIdleTimeout(33) + } + + @Test + fun `should return correct configuration for clusters from tag dependency where one service has multiple tags`() { + // given + val properties = SnapshotProperties() + val factory = EnvoyClustersFactory(properties) + val group = serviceGroup.copy( + proxySettings = ProxySettings( + outgoing = Outgoing( + serviceDependencies = listOf( + serviceDependency("service-A", 44) + ), + tagDependencies = listOf( + tagDependency("tag-1", 33) + ) + ) + ) + ) + val services = listOf("service-A", "service-B", "service-C") + val globalSnapshot = GlobalSnapshot( + clusters = createClusters(properties, services), + allServicesNames = services.toSet(), + endpoints = emptyMap(), + clusterConfigurations = emptyMap(), + securedClusters = emptyMap(), + tags = mapOf("service-A" to setOf("tag-1"), "service-C" to setOf("tag-1")) + ) + + // when + val clustersForGroup = factory.getClustersForGroup(group, globalSnapshot) + + // then + assertThat(clustersForGroup) + .hasSize(2) + .extracting { it.name } + .containsAll(listOf("service-A", "service-C")) + clustersForGroup.assertServiceCluster("service-A") + .hasIdleTimeout(44) + clustersForGroup.assertServiceCluster("service-C") + .hasIdleTimeout(33) + } + + @Test + fun `should return all clusters when is AllServiceGroup`() { + // given + val properties = SnapshotProperties() + val factory = EnvoyClustersFactory(properties) + val group = allServicesGroup + val services = listOf("service-A", "service-B", "service-C") + val globalSnapshot = GlobalSnapshot( + clusters = createClusters(properties, services), + allServicesNames = services.toSet(), + endpoints = emptyMap(), + clusterConfigurations = emptyMap(), + securedClusters = emptyMap(), + tags = emptyMap() + ) + + // when + val clustersForGroup = factory.getClustersForGroup(group, globalSnapshot) + + // then + assertThat(clustersForGroup) + .hasSize(3) + .extracting { it.name } + .containsAll(services) + } + + @Test + fun `should return all clusters when is AllServiceGroup and has service dependency`() { + val properties = SnapshotProperties() + val factory = EnvoyClustersFactory(properties) + val group = allServicesGroup.copy( + proxySettings = ProxySettings( + outgoing = Outgoing( + serviceDependencies = listOf( + serviceDependency("service-A", 34) + ) + ) + ) + ) + val services = listOf("service-A", "service-B", "service-C") + val globalSnapshot = GlobalSnapshot( + clusters = createClusters(properties, services), + allServicesNames = services.toSet(), + endpoints = emptyMap(), + clusterConfigurations = emptyMap(), + securedClusters = emptyMap(), + tags = emptyMap() + ) + + // when + val clustersForGroup = factory.getClustersForGroup(group, globalSnapshot) + + // then + assertThat(clustersForGroup) + .hasSize(3) + .extracting { it.name } + .containsAll(services) + clustersForGroup.assertServiceCluster("service-A") + .hasIdleTimeout(34) + } + + @Test + fun `should return all clusters when is AllServiceGroup and has tag dependency`() { + val properties = SnapshotProperties() + val factory = EnvoyClustersFactory(properties) + val group = allServicesGroup.copy( + proxySettings = ProxySettings( + outgoing = Outgoing( + tagDependencies = listOf( + tagDependency("tag", 27) + ) + ) + ) + ) + val services = listOf("service-A", "service-B", "service-C") + val globalSnapshot = GlobalSnapshot( + clusters = createClusters(properties, services), + allServicesNames = services.toSet(), + endpoints = emptyMap(), + clusterConfigurations = emptyMap(), + securedClusters = emptyMap(), + tags = mapOf("service-A" to setOf("tag")) + ) + + // when + val clustersForGroup = factory.getClustersForGroup(group, globalSnapshot) + + // then + assertThat(clustersForGroup) + .hasSize(3) + .extracting { it.name } + .containsAll(services) + clustersForGroup.assertServiceCluster("service-A") + .hasIdleTimeout(27) + + } + +} + +private fun List.assertServiceCluster(name: String): ObjectAssert { + return assertThat(this) + .filteredOn { it.name == name } + .hasSize(1) + .first() +} + +private fun ObjectAssert.hasIdleTimeout(idleTimeout: Long): ObjectAssert { + this.extracting { it.commonHttpProtocolOptions.idleTimeout.seconds } + .isEqualTo(idleTimeout) + return this } diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt index e635b32b9..641daf8e9 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt @@ -1,4 +1,100 @@ package pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.listeners +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test +import pl.allegro.tech.servicemesh.envoycontrol.groups.AccessLogFilterSettings +import pl.allegro.tech.servicemesh.envoycontrol.groups.AllServicesGroup +import pl.allegro.tech.servicemesh.envoycontrol.groups.CommunicationMode +import pl.allegro.tech.servicemesh.envoycontrol.groups.ListenersConfig +import pl.allegro.tech.servicemesh.envoycontrol.groups.Outgoing +import pl.allegro.tech.servicemesh.envoycontrol.groups.ProxySettings +import pl.allegro.tech.servicemesh.envoycontrol.groups.ServicesGroup +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.AccessLogFiltersProperties +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.GlobalSnapshot +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotProperties +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.createClusters +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.listeners.filters.EnvoyHttpFilters +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.serviceDependency +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.tagDependency + class EnvoyListenersFactoryTest { + + companion object { + val serviceGroup = ServicesGroup( + communicationMode = CommunicationMode.ADS, + serviceName = "service-name", + discoveryServiceName = "service-name", + listenersConfig = ListenersConfig( + ingressHost = "10.10.10.10", + ingressPort = 8888, + egressHost = "11.11.11.11", + egressPort = 9999, + accessLogFilterSettings = AccessLogFilterSettings(null, AccessLogFiltersProperties()), + useTransparentProxy = true + ) + ) + } + + @Test + fun `should return egress http proxy virtual listener listener with service dependency`() { + // given + val properties = SnapshotProperties() + val factory = EnvoyListenersFactory(properties, EnvoyHttpFilters(emptyList(), emptyList())) + val group = serviceGroup.copy( + proxySettings = ProxySettings( + outgoing = Outgoing( + serviceDependencies = listOf(serviceDependency("service-A", 33)) + ) + ) + ) + val services = listOf("service-A", "service-B", "service-C") + val globalSnapshot = GlobalSnapshot( + clusters = createClusters(properties, services), + allServicesNames = services.toSet(), + endpoints = emptyMap(), + clusterConfigurations = emptyMap(), + securedClusters = emptyMap(), + tags = emptyMap() + ) + + // when + val listeners = factory.createListeners(group, globalSnapshot) + + // then + Assertions.assertThat(listeners) + .extracting { it.name } + .contains("0.0.0.0:80") + } + + @Test + fun `should return egress http proxy virtual listener listener with tag dependency`() { + // given + val properties = SnapshotProperties() + val factory = EnvoyListenersFactory(properties, EnvoyHttpFilters(emptyList(), emptyList())) + val group = serviceGroup.copy( + proxySettings = ProxySettings( + outgoing = Outgoing( + tagDependencies = listOf(tagDependency("tag", 33)) + ) + ) + ) + val services = listOf("service-A", "service-B", "service-C") + val globalSnapshot = GlobalSnapshot( + clusters = createClusters(properties, services), + allServicesNames = services.toSet(), + endpoints = emptyMap(), + clusterConfigurations = emptyMap(), + securedClusters = emptyMap(), + tags = mapOf("service-B" to setOf("tag")) + ) + + // when + val listeners = factory.createListeners(group, globalSnapshot) + + // then + Assertions.assertThat(listeners) + .extracting { it.name } + .contains("0.0.0.0:80") + } + } diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/rbac/RBACFilterFactoryJwtTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/rbac/RBACFilterFactoryJwtTest.kt index aebc9238c..fc85ec50c 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/rbac/RBACFilterFactoryJwtTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/rbac/RBACFilterFactoryJwtTest.kt @@ -45,7 +45,8 @@ internal class RBACFilterFactoryJwtTest : RBACFilterFactoryTestUtils { setOf(), SnapshotResources.create(listOf(), "").resources(), mapOf(), - SnapshotResources.create(listOf(), "").resources() + SnapshotResources.create(listOf(), "").resources(), + emptyMap() ) @ParameterizedTest(name = "should generate RBAC rules for {arguments} OAuth Policy") diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/rbac/RBACFilterFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/rbac/RBACFilterFactoryTest.kt index e8ab1d68b..bb9988eb5 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/rbac/RBACFilterFactoryTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/rbac/RBACFilterFactoryTest.kt @@ -106,7 +106,8 @@ internal class RBACFilterFactoryTest : RBACFilterFactoryTestUtils { setOf(), SnapshotResources.create(listOf(), "").resources(), mapOf(), - SnapshotResources.create(listOf(), "").resources() + SnapshotResources.create(listOf(), "").resources(), + emptyMap() ) val clusterLoadAssignment = ClusterLoadAssignment.newBuilder() @@ -128,7 +129,8 @@ internal class RBACFilterFactoryTest : RBACFilterFactoryTestUtils { setOf(), SnapshotResources.create(listOf(clusterLoadAssignment), "").resources(), mapOf(), - SnapshotResources.create(listOf(), "").resources() + SnapshotResources.create(listOf(), "").resources(), + emptyMap() ) @Test diff --git a/envoy-control-runner/src/main/resources/application-docker.yaml b/envoy-control-runner/src/main/resources/application-docker.yaml index 4b6460432..c0b36f910 100644 --- a/envoy-control-runner/src/main/resources/application-docker.yaml +++ b/envoy-control-runner/src/main/resources/application-docker.yaml @@ -4,6 +4,12 @@ envoy-control: host: consul port: 8500 + envoy: + snapshot: + outgoing-permissions: + enabled: true + + chaos: username: "user" - password: "pass" \ No newline at end of file + password: "pass" diff --git a/envoy-control-runner/src/main/resources/application.yaml b/envoy-control-runner/src/main/resources/application.yaml index 46ea03a2c..c8d464064 100644 --- a/envoy-control-runner/src/main/resources/application.yaml +++ b/envoy-control-runner/src/main/resources/application.yaml @@ -8,6 +8,8 @@ envoy-control: consul: host: localhost + + chaos: username: "user" password: "pass" diff --git a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/OutgoingPermissionsTest.kt b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/OutgoingPermissionsTest.kt index 73f898820..7f71ad253 100644 --- a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/OutgoingPermissionsTest.kt +++ b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/OutgoingPermissionsTest.kt @@ -89,6 +89,8 @@ interface OutgoingPermissionsTest { // given consul().server.operations.registerService(service(), name = "not-accessible") consul().server.operations.registerService(service(), name = "echo") + consul().server.operations.registerService(service(), name = "tag-service-A", tags = listOf("scope")) + consul().server.operations.registerService(service(), name = "tag-service-B", tags = listOf("scope")) untilAsserted { // when @@ -99,6 +101,8 @@ interface OutgoingPermissionsTest { val reachableResponseEchoWithDomain2 = envoy().egressOperations.callService("echo.domain") val reachableDomainResponse = envoy().egressOperations.callDomain("www.example.com") val unreachableDomainResponse = envoy().egressOperations.callDomain("www.another-example.com") + val reachableFirstTagResponse = envoy().egressOperations.callService("tag-service-A") + val reachableSecondTagResponse = envoy().egressOperations.callService("tag-service-B") // then assertThat(reachableResponse).isOk().isFrom(service()) @@ -108,6 +112,8 @@ interface OutgoingPermissionsTest { assertThat(unreachableDomainResponse).isUnreachable() assertThat(unreachableResponse).isUnreachable() assertThat(unregisteredResponse).isUnreachable() + assertThat(reachableFirstTagResponse).isOk().isFrom(service()) + assertThat(reachableSecondTagResponse).isOk().isFrom(service()) } } } diff --git a/envoy-control-tests/src/main/resources/envoy/config_ads.yaml b/envoy-control-tests/src/main/resources/envoy/config_ads.yaml index 5eb15b086..9b6a315a5 100644 --- a/envoy-control-tests/src/main/resources/envoy/config_ads.yaml +++ b/envoy-control-tests/src/main/resources/envoy/config_ads.yaml @@ -63,6 +63,7 @@ node: - domain: "https://www.example.com" - domain: "https://www.example-redirect.com" handleInternalRedirect: true + - tag: scope static_resources: clusters: diff --git a/envoy-control-tests/src/main/resources/envoy/config_xds.yaml b/envoy-control-tests/src/main/resources/envoy/config_xds.yaml index 511b28e73..87588cef8 100644 --- a/envoy-control-tests/src/main/resources/envoy/config_xds.yaml +++ b/envoy-control-tests/src/main/resources/envoy/config_xds.yaml @@ -63,6 +63,7 @@ node: - domain: "https://www.example.com" - domain: "https://www.example-redirect.com" handleInternalRedirect: true + - tag: scope static_resources: clusters: From ae3543646a0cb40f730066b0f7725e5b1572fae1 Mon Sep 17 00:00:00 2001 From: Kamil Smigielski Date: Thu, 15 Dec 2022 08:58:26 +0100 Subject: [PATCH 03/15] prepare PR --- CHANGELOG.md | 1 + docs/configuration.md | 4 +- .../resource/clusters/EnvoyClustersFactory.kt | 3 +- tools/docker-compose.yaml | 3 + tools/envoy-control/Dockerfile | 18 ++++-- tools/envoy-control/application-docker.yaml | 9 --- tools/envoy/envoy-template.yaml | 2 +- tools/service/register_and_run.sh | 60 ++++--------------- 8 files changed, 33 insertions(+), 67 deletions(-) delete mode 100644 tools/envoy-control/application-docker.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index ddaea3b89..85efbe63b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Changed +- Add possibility to set dependency to group of services by tag mechanism ## [0.19.26] diff --git a/docs/configuration.md b/docs/configuration.md index 68877aad6..4da54ea06 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -95,7 +95,7 @@ Property ## Permissions Property | Description | Default value --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- +-------------------------------------------------------------------------------------------------------------------------------------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --------- **envoy-control.envoy.snapshot.incoming-permissions.enabled** | Enable incoming permissions | false **envoy-control.envoy.snapshot.incoming-permissions.client-identity-headers** | Headers that identify the client calling the endpoint. In most cases `client-identity-header` should include `service-name-header` value to correctly identify other services in the mesh. | [ x-service-name ] **envoy-control.envoy.snapshot.incoming-permissions.clients-allowed-to-all-endpoints** | Client names which are allowed to even call service if incoming permissions are enabled. | empty list @@ -127,7 +127,7 @@ Property **envoy-control.envoy.snapshot.outgoing-permissions.services-allowed-to-use-wildcard** | Services that are allowed to have wildcard in outgoing.dependency field | empty set **envoy-control.envoy.snapshot.outgoing-permissions.rbac.clients-lists.default-clients-list** | List of clients which will be applied to each rbac policy, if none of the lists defined in `custom-clients-lists` have been matched | empty list **envoy-control.envoy.snapshot.outgoing-permissions.rbac.clients-lists.custom-clients-lists** | Lists of clients which will be applied to each rbac policy, only if key for defined list is present in clients for defined endpoint | empty map - +**envoy-control.envoy.snapshot.outgoing-permissions.tag-prefix** | Value that specify which tags are allowed to be used in dependencies by prefix | empty string ## Load Balancing Property | Description | Default value ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt index 4506a7f54..33eb5f1e6 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt @@ -111,8 +111,7 @@ class EnvoyClustersFactory( fun getClustersForGroup(group: Group, globalSnapshot: GlobalSnapshot): List = getEdsClustersForGroup(group, globalSnapshot) + - getStrictDnsClustersForGroup(group) + - clustersForJWT + + getStrictDnsClustersForGroup(group) + clustersForJWT + getRateLimitClusterForGroup(group, globalSnapshot) private fun clusterForOAuthProvider(provider: OAuthProvider): Cluster? { diff --git a/tools/docker-compose.yaml b/tools/docker-compose.yaml index 0d5b71adb..05bbcde69 100644 --- a/tools/docker-compose.yaml +++ b/tools/docker-compose.yaml @@ -36,6 +36,9 @@ services: ports: - "8080:8080" - "50000:50000" + # here you can define path to your own config + volumes: + - "../envoy-control-runner/src/main/resources/application-docker.yaml:/var/tmp/config/application.yaml" depends_on: - consul environment: diff --git a/tools/envoy-control/Dockerfile b/tools/envoy-control/Dockerfile index 5060cd793..790daa9f0 100644 --- a/tools/envoy-control/Dockerfile +++ b/tools/envoy-control/Dockerfile @@ -1,13 +1,23 @@ +FROM gradle:6.6.1-jdk11 AS builder +COPY --chown=gradle:gradle settings.gradle build.gradle gradle.properties /home/gradle/src/ +COPY --chown=gradle:gradle envoy-control-core/ /home/gradle/src/envoy-control-core/ +COPY --chown=gradle:gradle envoy-control-runner/ /home/gradle/src/envoy-control-runner/ +COPY --chown=gradle:gradle envoy-control-services/ /home/gradle/src/envoy-control-services/ +COPY --chown=gradle:gradle envoy-control-source-consul/ /home/gradle/src/envoy-control-source-consul/ + +WORKDIR /home/gradle/src +RUN gradle :envoy-control-runner:assemble --parallel --no-daemon + FROM adoptopenjdk/openjdk11:alpine-jre RUN mkdir /tmp/envoy-control-dist /tmp/envoy-control /bin/envoy-control /etc/envoy-control /var/tmp/config -COPY tools/envoy-control/run.sh /usr/local/bin/run.sh -VOLUME /var/tmp/config -COPY ../../envoy-control-runner/build/distributions /tmp/envoy-control-dist +COPY --from=builder /home/gradle/src/envoy-control-runner/build/distributions/ /tmp/envoy-control-dist COPY ./envoy-control-runner/src/main/resources/application-docker.yaml /etc/envoy-control/application.yaml -RUN tar -xf /tmp/envoy-control-dist/envoy-control-runner-*.tar -C /tmp/envoy-control \ +RUN tar -xf /tmp/envoy-control-dist/envoy-control-runner-0.1.0*.tar -C /tmp/envoy-control \ && mv /tmp/envoy-control/envoy-control-runner*/ /bin/envoy-control/envoy-control-runner +COPY tools/envoy-control/run.sh /usr/local/bin/run.sh +VOLUME /var/tmp/config WORKDIR /usr/local/bin/ # APP_PORT: 8080 diff --git a/tools/envoy-control/application-docker.yaml b/tools/envoy-control/application-docker.yaml deleted file mode 100644 index 9a720d978..000000000 --- a/tools/envoy-control/application-docker.yaml +++ /dev/null @@ -1,9 +0,0 @@ -envoy-control: - source: - consul: - host: consul - port: 8500 - -chaos: - username: "user" - password: "pass" diff --git a/tools/envoy/envoy-template.yaml b/tools/envoy/envoy-template.yaml index 5bd0b057a..232a2ccba 100644 --- a/tools/envoy/envoy-template.yaml +++ b/tools/envoy/envoy-template.yaml @@ -10,7 +10,7 @@ node: proxy_settings: outgoing: dependencies: - - tag: hermes-consumers + - service: "*" locality: zone: default-zone diff --git a/tools/service/register_and_run.sh b/tools/service/register_and_run.sh index d04b8a511..0602041d9 100644 --- a/tools/service/register_and_run.sh +++ b/tools/service/register_and_run.sh @@ -4,33 +4,20 @@ set -o pipefail set -o errexit port=80 -service_name1=http-echo1 -service_name2=http-echo2 -service_name3=http-echo3 +service_name=http-echo +instance_id="${service_name}-1" + +echo "Registering instance of ${service_name} in consul" +echo "=============================" +echo +echo ip="$(hostname -i)" -body1=' -{ - "ID": "'${service_name1}'-1", - "Name": "'${service_name1}'", - "Tags": [ - "primary", - "hermes-consumers" - ], - "Address": "'${ip}'", - "Port": '${port}', - "Check": { - "DeregisterCriticalServiceAfter": "90m", - "http": "http://'${ip}:${port}'", - "Interval": "10s" - } -} -' -body2=' +body=' { - "ID": "'${service_name2}'-1", - "Name": "'${service_name2}'", + "ID": "'${instance_id}'", + "Name": "'${service_name}'", "Tags": [ "primary" ], @@ -43,32 +30,7 @@ body2=' } } ' -body3=' -{ - "ID": "'${service_name3}'-1", - "Name": "'${service_name3}'", - "Tags": [ - "primary", - "hermes-consumers" - ], - "Address": "'${ip}'", - "Port": '${port}', - "Check": { - "DeregisterCriticalServiceAfter": "90m", - "http": "http://'${ip}:${port}'", - "Interval": "10s" - } -} -' - -sleep 15 - -echo "Registering instance of ${service_name1} in consul" -curl -X PUT --data "${body1}" consul:8500/v1/agent/service/register -echo "Registering instance of ${service_name2} in consul" -curl -X PUT --data "${body2}" consul:8500/v1/agent/service/register -echo "Registering instance of ${service_name3} in consul" -curl -X PUT --data "${body3}" consul:8500/v1/agent/service/register +curl -X PUT --fail --data "${body}" -s consul:8500/v1/agent/service/register cd /app node ./index.js From d0c1436fcb157816a6e2fd998dd459f1a4e9bfba Mon Sep 17 00:00:00 2001 From: Kamil Smigielski Date: Thu, 15 Dec 2022 09:04:06 +0100 Subject: [PATCH 04/15] prepare PR --- docs/features/permissions.md | 1 + .../src/main/resources/application-docker.yaml | 6 ------ envoy-control-runner/src/main/resources/application.yaml | 2 -- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/features/permissions.md b/docs/features/permissions.md index 06ee90807..433244a77 100644 --- a/docs/features/permissions.md +++ b/docs/features/permissions.md @@ -24,6 +24,7 @@ metadata: - service: service-b handleInternalRedirect: true - domain: http://www.example.com + - tag: tag-a incoming: endpoints: - path: /example diff --git a/envoy-control-runner/src/main/resources/application-docker.yaml b/envoy-control-runner/src/main/resources/application-docker.yaml index c0b36f910..9a720d978 100644 --- a/envoy-control-runner/src/main/resources/application-docker.yaml +++ b/envoy-control-runner/src/main/resources/application-docker.yaml @@ -4,12 +4,6 @@ envoy-control: host: consul port: 8500 - envoy: - snapshot: - outgoing-permissions: - enabled: true - - chaos: username: "user" password: "pass" diff --git a/envoy-control-runner/src/main/resources/application.yaml b/envoy-control-runner/src/main/resources/application.yaml index c8d464064..46ea03a2c 100644 --- a/envoy-control-runner/src/main/resources/application.yaml +++ b/envoy-control-runner/src/main/resources/application.yaml @@ -8,8 +8,6 @@ envoy-control: consul: host: localhost - - chaos: username: "user" password: "pass" From 4b981614b40c60d129299aade9e54d980cc066e8 Mon Sep 17 00:00:00 2001 From: Kamil Smigielski Date: Thu, 15 Dec 2022 09:08:27 +0100 Subject: [PATCH 05/15] prepare PR --- .../allegro/tech/servicemesh/envoycontrol/ControlPlane.kt | 5 ++++- .../servicemesh/envoycontrol/groups/MetadataNodeGroup.kt | 3 --- .../tech/servicemesh/envoycontrol/groups/NodeMetadata.kt | 8 +++++++- .../envoycontrol/snapshot/EnvoySnapshotFactory.kt | 6 +++++- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/ControlPlane.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/ControlPlane.kt index 10ce38b28..3cffc8c62 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/ControlPlane.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/ControlPlane.kt @@ -168,7 +168,10 @@ class ControlPlane private constructor( val snapshotProperties = properties.envoy.snapshot val envoySnapshotFactory = EnvoySnapshotFactory( ingressRoutesFactory = EnvoyIngressRoutesFactory(snapshotProperties, envoyHttpFilters), - egressRoutesFactory = EnvoyEgressRoutesFactory(snapshotProperties.egress, snapshotProperties.incomingPermissions), + egressRoutesFactory = EnvoyEgressRoutesFactory( + snapshotProperties.egress, + snapshotProperties.incomingPermissions + ), clustersFactory = EnvoyClustersFactory(snapshotProperties), endpointsFactory = EnvoyEndpointsFactory( snapshotProperties, ServiceTagMetadataGenerator(snapshotProperties.routing.serviceTags) diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/MetadataNodeGroup.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/MetadataNodeGroup.kt index 8a8169da7..f25228335 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/MetadataNodeGroup.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/MetadataNodeGroup.kt @@ -135,7 +135,6 @@ class MetadataNodeGroup( val proxySettings = proxySettings(nodeMetadata) val listenersConfig = createListenersConfig(node.id, node.metadata) - println("ksksks hasAllServicesDependencies(nodeMetadata) ${hasAllServicesDependencies(nodeMetadata)}") return when { hasAllServicesDependencies(nodeMetadata) -> AllServicesGroup( @@ -157,8 +156,6 @@ class MetadataNodeGroup( } private fun hasAllServicesDependencies(metadata: NodeMetadata): Boolean { - println("ksksks !properties.outgoingPermissions.enabled ${!properties.outgoingPermissions.enabled}") - println("ksksks metadata.proxySettings.outgoing.allServicesDependencies ${metadata.proxySettings.outgoing.allServicesDependencies}") return !properties.outgoingPermissions.enabled || metadata.proxySettings.outgoing.allServicesDependencies } diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadata.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadata.kt index eb6c465db..6437aad7b 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadata.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadata.kt @@ -93,7 +93,13 @@ fun Value?.toHeaderFilter(default: String? = null): HeaderFilterSettings? { } } -private class RawDependency(val service: String?, val domain: String?, val domainPattern: String?, val tag: String?, val value: Value) +private class RawDependency( + val service: String?, + val domain: String?, + val domainPattern: String?, + val tag: String?, + val value: Value +) fun Value?.toOutgoing(properties: SnapshotProperties): Outgoing { val allServiceDependenciesIdentifier = properties.outgoingPermissions.allServicesDependencies.identifier diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt index 0e1eca658..e01d0d7d9 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt @@ -24,6 +24,7 @@ import pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.routes.EnvoyEg import pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.routes.EnvoyIngressRoutesFactory import java.util.SortedMap +@Suppress("LongParameterList") class EnvoySnapshotFactory( private val ingressRoutesFactory: EnvoyIngressRoutesFactory, private val egressRoutesFactory: EnvoyEgressRoutesFactory, @@ -188,7 +189,10 @@ class EnvoySnapshotFactory( // TODO(dj): This is where serious refactoring needs to be done val egressDomainRouteSpecifications = routeSpecificationFactory.domainRouteSpecifications(group) - val egressServiceRouteSpecification = routeSpecificationFactory.serviceRouteSpecifications(group, globalSnapshot) + val egressServiceRouteSpecification = routeSpecificationFactory.serviceRouteSpecifications( + group, + globalSnapshot + ) val egressRouteSpecification = egressServiceRouteSpecification + egressDomainRouteSpecifications.values.flatten().toSet() + routeSpecificationFactory.domainPatternRouteSpecifications(group) From 081a17d6469589d23ac5275c4c934f80e8b5999c Mon Sep 17 00:00:00 2001 From: Kamil Smigielski Date: Thu, 15 Dec 2022 09:11:16 +0100 Subject: [PATCH 06/15] prepare PR --- .../servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt | 4 ++-- .../snapshot/resource/listeners/EnvoyListenersFactory.kt | 1 - .../snapshot/resource/listeners/filters/AccessLogFilter.kt | 1 - .../snapshot/resource/routes/EnvoyEgressRoutesFactory.kt | 2 -- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt index e01d0d7d9..e7b955a7a 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt @@ -40,8 +40,8 @@ class EnvoySnapshotFactory( companion object { const val DEFAULT_HTTP_PORT = 80 - internal fun tagExtractor(tagPrefix: String, servicesStates: MultiClusterState): Map> - = servicesStates.flatMap { it.servicesState.serviceNameToInstances.asIterable() } + internal fun tagExtractor(tagPrefix: String, servicesStates: MultiClusterState): Map> = + servicesStates.flatMap { it.servicesState.serviceNameToInstances.asIterable() } .fold(emptyMap()) { acc, entry -> val value = acc.getOrDefault(entry.key, emptySet()) diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactory.kt index c91401c6e..a88e52f26 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactory.kt @@ -22,7 +22,6 @@ import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.SdsSecretConfig import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsParameters -import pl.allegro.tech.servicemesh.envoycontrol.groups.Dependency import pl.allegro.tech.servicemesh.envoycontrol.groups.DomainDependency import pl.allegro.tech.servicemesh.envoycontrol.groups.Group import pl.allegro.tech.servicemesh.envoycontrol.groups.ListenersConfig diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/AccessLogFilter.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/AccessLogFilter.kt index d5d4848da..e5c001032 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/AccessLogFilter.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/AccessLogFilter.kt @@ -18,7 +18,6 @@ import io.envoyproxy.envoy.extensions.access_loggers.file.v3.FileAccessLog import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher import pl.allegro.tech.servicemesh.envoycontrol.groups.AccessLogFilterSettings import pl.allegro.tech.servicemesh.envoycontrol.snapshot.AccessLogProperties -import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotProperties import pl.allegro.tech.servicemesh.envoycontrol.utils.ComparisonFilterSettings import pl.allegro.tech.servicemesh.envoycontrol.utils.HeaderFilterSettings diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/routes/EnvoyEgressRoutesFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/routes/EnvoyEgressRoutesFactory.kt index eab4b3849..4aed85859 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/routes/EnvoyEgressRoutesFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/routes/EnvoyEgressRoutesFactory.kt @@ -11,7 +11,6 @@ import io.envoyproxy.envoy.config.route.v3.HeaderMatcher import io.envoyproxy.envoy.config.route.v3.InternalRedirectPolicy import io.envoyproxy.envoy.config.route.v3.RetryPolicy import io.envoyproxy.envoy.config.route.v3.RetryPolicy.ResetHeaderFormat -import io.envoyproxy.envoy.config.route.v3.RetryPolicy.RetryPriority import io.envoyproxy.envoy.config.route.v3.Route import io.envoyproxy.envoy.config.route.v3.RouteAction import io.envoyproxy.envoy.config.route.v3.RouteConfiguration @@ -24,7 +23,6 @@ import pl.allegro.tech.servicemesh.envoycontrol.groups.RetryHostPredicate import pl.allegro.tech.servicemesh.envoycontrol.snapshot.EgressProperties import pl.allegro.tech.servicemesh.envoycontrol.snapshot.IncomingPermissionsProperties import pl.allegro.tech.servicemesh.envoycontrol.snapshot.RouteSpecification -import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotProperties import pl.allegro.tech.servicemesh.envoycontrol.groups.RetryPolicy as EnvoyControlRetryPolicy class EnvoyEgressRoutesFactory( From 040ab4d225a933422d145b299275619fefbab24d Mon Sep 17 00:00:00 2001 From: Kamil Smigielski Date: Thu, 15 Dec 2022 09:31:08 +0100 Subject: [PATCH 07/15] prepare PR --- .../envoycontrol/EnvoySnapshotFactoryTest.kt | 11 +---------- .../envoycontrol/groups/NodeMetadataTest.kt | 2 -- .../envoycontrol/groups/NodeMetadataValidatorTest.kt | 4 ---- 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/EnvoySnapshotFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/EnvoySnapshotFactoryTest.kt index f550024e4..55f8327b5 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/EnvoySnapshotFactoryTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/EnvoySnapshotFactoryTest.kt @@ -349,7 +349,7 @@ class EnvoySnapshotFactoryTest { emptyList(), emptyList() ) { Metadata.getDefaultInstance() } ) - val egressRoutesFactory = EnvoyEgressRoutesFactory(properties) + val egressRoutesFactory = EnvoyEgressRoutesFactory(properties.egress, properties.incomingPermissions) val clustersFactory = EnvoyClustersFactory(properties) val endpointsFactory = EnvoyEndpointsFactory(properties, ServiceTagMetadataGenerator()) val envoyHttpFilters = EnvoyHttpFilters.defaultFilters(properties) @@ -404,12 +404,3 @@ class EnvoySnapshotFactoryTest { .build() } } - -class RouteSpecificationFactoryTest { - - @Test - fun `a`() { - - } - -} diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataTest.kt index 856040478..ed88bf5b0 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataTest.kt @@ -1142,7 +1142,6 @@ class AllServiceDependencyTest { .isEqualTo("Unsupported 'all serviceDependencies identifier' for domain dependency: *") assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) } - } class DomainPatternDependencyTest { @@ -1176,7 +1175,6 @@ class DomainPatternDependencyTest { ) assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) } - } class TagDependencyTest { diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataValidatorTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataValidatorTest.kt index b660796cb..cfce239f0 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataValidatorTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataValidatorTest.kt @@ -2,9 +2,7 @@ package pl.allegro.tech.servicemesh.envoycontrol.groups import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest import io.grpc.Status -import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType -import org.assertj.core.api.Assertions.catchThrowable import org.junit.jupiter.api.Assertions.assertDoesNotThrow import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest @@ -303,7 +301,6 @@ class NodeMetadataValidatorTest { this.xds = xds } - private fun NodeMetadataValidator.assertThrow( request: DiscoveryRequest, exceptionClass: Class, @@ -313,7 +310,6 @@ class NodeMetadataValidatorTest { .matches { it.status.description == description } .matches { it.status.code == Status.Code.INVALID_ARGUMENT } - private fun NodeMetadataValidator.notThrow(request: DiscoveryRequest) = assertDoesNotThrow { this.onV3StreamRequest(123, request) } From 97782f34e245257cc52d74c4cb9b5a3d7beaae35 Mon Sep 17 00:00:00 2001 From: Kamil Smigielski Date: Thu, 15 Dec 2022 09:35:47 +0100 Subject: [PATCH 08/15] prepare PR --- .../envoycontrol/snapshot/resource/ResourceUtils.kt | 1 - .../snapshot/resource/clusters/EnvoyClusterFactoryTest.kt | 4 +--- .../snapshot/resource/listeners/EnvoyListenersFactoryTest.kt | 2 -- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/ResourceUtils.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/ResourceUtils.kt index 410f3b1b7..ddc8fbe11 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/ResourceUtils.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/ResourceUtils.kt @@ -11,7 +11,6 @@ import pl.allegro.tech.servicemesh.envoycontrol.groups.Outgoing import pl.allegro.tech.servicemesh.envoycontrol.groups.ServiceDependency import pl.allegro.tech.servicemesh.envoycontrol.groups.TagDependency import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotProperties -import pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.clusters.EnvoyClusterFactoryTest fun createClusters( properties: SnapshotProperties, diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt index e270bd788..aea1457ec 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt @@ -1,4 +1,4 @@ -package pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.clusters; +package pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.clusters import io.envoyproxy.envoy.config.cluster.v3.Cluster import org.assertj.core.api.Assertions.assertThat @@ -276,9 +276,7 @@ class EnvoyClusterFactoryTest { .containsAll(services) clustersForGroup.assertServiceCluster("service-A") .hasIdleTimeout(27) - } - } private fun List.assertServiceCluster(name: String): ObjectAssert { diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt index 641daf8e9..6ef105586 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt @@ -3,7 +3,6 @@ package pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.listeners import org.assertj.core.api.Assertions import org.junit.jupiter.api.Test import pl.allegro.tech.servicemesh.envoycontrol.groups.AccessLogFilterSettings -import pl.allegro.tech.servicemesh.envoycontrol.groups.AllServicesGroup import pl.allegro.tech.servicemesh.envoycontrol.groups.CommunicationMode import pl.allegro.tech.servicemesh.envoycontrol.groups.ListenersConfig import pl.allegro.tech.servicemesh.envoycontrol.groups.Outgoing @@ -96,5 +95,4 @@ class EnvoyListenersFactoryTest { .extracting { it.name } .contains("0.0.0.0:80") } - } From 10f9a0bf9d22e098063cdc79f1cf0de2cf1c957a Mon Sep 17 00:00:00 2001 From: Kamil Smigielski Date: Mon, 19 Dec 2022 22:08:18 +0100 Subject: [PATCH 09/15] CR --- .../groups/NodeMetadataValidator.kt | 4 +- .../snapshot/EnvoySnapshotFactory.kt | 42 ++++----- .../envoycontrol/snapshot/GlobalSnapshot.kt | 15 +++- .../resource/clusters/EnvoyClustersFactory.kt | 44 ++++------ .../snapshot/EnvoySnapshotFactoryTest.kt | 15 ++-- .../snapshot/SnapshotUpdaterTest.kt | 5 +- .../clusters/EnvoyClusterFactoryTest.kt | 88 ++++++++----------- .../listeners/EnvoyListenersFactoryTest.kt | 4 +- .../routes/EnvoyEgressRoutesFactoryTest.kt | 53 +++++++---- 9 files changed, 135 insertions(+), 135 deletions(-) diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataValidator.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataValidator.kt index e70019d63..f3d335f58 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataValidator.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/groups/NodeMetadataValidator.kt @@ -110,7 +110,9 @@ class NodeMetadataValidator( val unsupportedTags = metadata.proxySettings.outgoing.getTagDependencies() .filter { !it.tag.startsWith(properties.outgoingPermissions.tagPrefix) } .map { it.tag } - throw TagDependencyValidationException(metadata.serviceName, unsupportedTags) + if (unsupportedTags.isNotEmpty()) { + throw TagDependencyValidationException(metadata.serviceName, unsupportedTags) + } } } diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt index e7b955a7a..cb23eda78 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt @@ -161,13 +161,9 @@ class EnvoySnapshotFactory( } } - fun getSnapshotForGroup(group: Group, globalSnapshot: GlobalSnapshot): Snapshot { - val groupSample = Timer.start(meterRegistry) - - val newSnapshotForGroup = newSnapshotForGroup(group, globalSnapshot) - groupSample.stop(meterRegistry.timer("snapshot-factory.get-snapshot-for-group.time")) - return newSnapshotForGroup - } + fun getSnapshotForGroup(group: Group, globalSnapshot: GlobalSnapshot): Snapshot = + meterRegistry.timer("snapshot-factory.get-snapshot-for-group.time") + .record { newSnapshotForGroup(group, globalSnapshot) } private fun getServicesEndpointsForGroup( rateLimitEndpoints: List, @@ -333,31 +329,23 @@ class RouteSpecificationFactory( ) } val servicesNames = group.proxySettings.outgoing.getServiceDependencies().map { it.service }.toSet() - val definedTagsRoutes = group.proxySettings.outgoing.getTagDependencies().flatMap { tagDependency -> - globalSnapshot.tags - .filterKeys { !servicesNames.contains(it) } - .filterValues { it.contains(tagDependency.tag) } - .map { RouteSpecification( - clusterName = it.key, - routeDomains = listOf(it.key) + getServiceWithCustomDomain(it.key), - settings = tagDependency.settings - ) } - }.fold(emptyMap()) { - acc, routeSpecification -> - if (acc.containsKey(routeSpecification.clusterName)) { - acc - } else { - acc.plus(routeSpecification.clusterName to routeSpecification) - } - } + val definedTagsRoutes = globalSnapshot + .getTagsForDependency(group.proxySettings.outgoing) { servicesName, tagDependency -> + RouteSpecification( + clusterName = servicesName, + routeDomains = listOf(servicesName) + getServiceWithCustomDomain(servicesName), + settings = tagDependency.settings + ) + }.distinctBy { it.clusterName } + return when (group) { is ServicesGroup -> { - definedServicesRoutes + definedTagsRoutes.values + definedServicesRoutes + definedTagsRoutes } is AllServicesGroup -> { val allServicesRoutes = globalSnapshot.allServicesNames .subtract(servicesNames) - .subtract(definedTagsRoutes.keys) + .subtract(definedTagsRoutes.map { it.clusterName }.toSet()) .map { RouteSpecification( clusterName = it, @@ -365,7 +353,7 @@ class RouteSpecificationFactory( settings = group.proxySettings.outgoing.defaultServiceSettings ) } - allServicesRoutes + definedServicesRoutes + definedTagsRoutes.values + allServicesRoutes + definedServicesRoutes + definedTagsRoutes } } } diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/GlobalSnapshot.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/GlobalSnapshot.kt index a498221ee..d65f19662 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/GlobalSnapshot.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/GlobalSnapshot.kt @@ -3,6 +3,8 @@ package pl.allegro.tech.servicemesh.envoycontrol.snapshot import io.envoyproxy.controlplane.cache.SnapshotResources import io.envoyproxy.envoy.config.cluster.v3.Cluster import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment +import pl.allegro.tech.servicemesh.envoycontrol.groups.Outgoing +import pl.allegro.tech.servicemesh.envoycontrol.groups.TagDependency data class GlobalSnapshot( val clusters: Map, @@ -11,7 +13,18 @@ data class GlobalSnapshot( val clusterConfigurations: Map, val securedClusters: Map, val tags: Map> -) +) { + fun getTagsForDependency( + outgoing: Outgoing, + mapper: (String, TagDependency) -> T): List { + val serviceDependencies = outgoing.getServiceDependencies().map { it.service }.toSet() + return outgoing.getTagDependencies().flatMap { tagDependency -> + tags.filterKeys { !serviceDependencies.contains(it) } + .filterValues { it.contains(tagDependency.tag) } + .map { mapper(it.key, tagDependency) } + } + } +} @Suppress("LongParameterList") fun globalSnapshot( diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt index 33eb5f1e6..08e14f688 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt @@ -191,36 +191,30 @@ class EnvoyClustersFactory( globalSnapshot.clusters } - val serviceDependencies = group.proxySettings.outgoing.getServiceDependencies().mapNotNull { - createClusterForGroup(it.settings, clusters[it.service]) - } - val servicesNames = group.proxySettings.outgoing.getServiceDependencies().map { it.service }.toSet() - - val tagDependencies = group.proxySettings.outgoing.getTagDependencies().flatMap { tagDependency -> - globalSnapshot.tags - .filterKeys { !servicesNames.contains(it) } - .filterValues { it.contains(tagDependency.tag) } - .map { - it.key to createClusterForGroup(tagDependency.settings, clusters[it.key]) - }.toMap().asIterable() - }.fold(emptyMap()) { - acc, entry -> - if (acc.containsKey(entry.key) || entry.value == null) { - acc - } else { - acc.plus(entry.key to entry.value!!) - } - } + val serviceSettings = group.proxySettings.outgoing.getServiceDependencies() + .associateBy({ it.service }, { it.settings }) + + val serviceFromTagSettings = globalSnapshot + .getTagsForDependency(group.proxySettings.outgoing) { serviceName, tagDependency -> + serviceName to tagDependency.settings + }.reversed().associateBy({ it.first }, { it.second }) val clustersForGroup = when (group) { - is ServicesGroup -> serviceDependencies + tagDependencies.values + is ServicesGroup -> serviceSettings.mapNotNull { + createClusterForGroup(it.value, clusters[it.key]) + } + serviceFromTagSettings.mapNotNull { + createClusterForGroup(it.value, clusters[it.key]) + } is AllServicesGroup -> { globalSnapshot.allServicesNames - .subtract(servicesNames) - .subtract(tagDependencies.keys) .mapNotNull { - createClusterForGroup(group.proxySettings.outgoing.defaultServiceSettings, clusters[it]) - } + serviceDependencies + tagDependencies.values + val settings = serviceSettings[it] ?: serviceFromTagSettings[it] + if (settings != null && settings.timeoutPolicy.connectionIdleTimeout != null) { + createClusterForGroup(settings, clusters[it]) + } else { + createClusterForGroup(group.proxySettings.outgoing.defaultServiceSettings, clusters[it]) + } + } } } diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt index 53e5c68e8..b7e64fc26 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt @@ -7,7 +7,6 @@ import pl.allegro.tech.servicemesh.envoycontrol.services.Locality import pl.allegro.tech.servicemesh.envoycontrol.services.MultiClusterState import pl.allegro.tech.servicemesh.envoycontrol.services.ServiceInstance import pl.allegro.tech.servicemesh.envoycontrol.services.ServiceInstances -import pl.allegro.tech.servicemesh.envoycontrol.services.ServiceName import pl.allegro.tech.servicemesh.envoycontrol.services.ServicesState import java.util.concurrent.ConcurrentHashMap @@ -54,7 +53,7 @@ class EnvoySnapshotFactoryTest { val tagPrefix = "" val serviceTagsCluster1 = mapOf("abc" to setOf("uws", "poc"), "xyz" to setOf("uj"), "qwerty" to setOf()) val serviceTagsCluster2 = mapOf("abc" to setOf("lkj"), "xyz" to setOf(), "qwerty" to setOf("ban")) - val state: MultiClusterState = MultiClusterState(listOf( + val state = MultiClusterState(listOf( ClusterState(serviceState(serviceTagsCluster1), Locality.LOCAL, "cluster"), ClusterState(serviceState(serviceTagsCluster2), Locality.LOCAL, "cluster2") )) @@ -71,13 +70,9 @@ class EnvoySnapshotFactoryTest { } private fun serviceState(servicesTags: Map>): ServicesState { - val map: ConcurrentHashMap = ConcurrentHashMap() - servicesTags.forEach { - val instances = listOf(1, 2, 3).map { id -> - ServiceInstance("${it.key}-$id", it.value, null, null) - }.toSet() - map[it.key] = ServiceInstances(it.key, instances) - } - return ServicesState(map) + val servicesInstances = servicesTags.map { + it.key to setOf(ServiceInstance("${it.key}-1", it.value, null, null)) + }.associateTo(ConcurrentHashMap()) { it.first to ServiceInstances(it.first, it.second) } + return ServicesState(servicesInstances) } } diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotUpdaterTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotUpdaterTest.kt index 3ffe4fa87..cfc70af0d 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotUpdaterTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotUpdaterTest.kt @@ -1310,7 +1310,10 @@ class SnapshotUpdaterTest { private fun snapshotFactory(snapshotProperties: SnapshotProperties, meterRegistry: MeterRegistry) = EnvoySnapshotFactory( ingressRoutesFactory = EnvoyIngressRoutesFactory(snapshotProperties), - egressRoutesFactory = EnvoyEgressRoutesFactory(snapshotProperties), + egressRoutesFactory = EnvoyEgressRoutesFactory( + snapshotProperties.egress, + snapshotProperties.incomingPermissions + ), clustersFactory = EnvoyClustersFactory(snapshotProperties), endpointsFactory = EnvoyEndpointsFactory( snapshotProperties, ServiceTagMetadataGenerator(snapshotProperties.routing.serviceTags) diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt index aea1457ec..276d4bebb 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt @@ -44,13 +44,9 @@ class EnvoyClusterFactoryTest { ) ) val services = listOf("service-A", "service-B", "service-C") - val globalSnapshot = GlobalSnapshot( - clusters = createClusters(properties, services), - allServicesNames = services.toSet(), - endpoints = emptyMap(), - clusterConfigurations = emptyMap(), - securedClusters = emptyMap(), - tags = emptyMap() + val globalSnapshot = buildGlobalSnapshot( + services = services, + properties = properties ) // when @@ -77,12 +73,9 @@ class EnvoyClusterFactoryTest { ) ) val services = listOf("service-A", "service-B", "service-C") - val globalSnapshot = GlobalSnapshot( - clusters = createClusters(properties, services), - allServicesNames = services.toSet(), - endpoints = emptyMap(), - clusterConfigurations = emptyMap(), - securedClusters = emptyMap(), + val globalSnapshot = buildGlobalSnapshot( + services = services, + properties = properties, tags = mapOf("service-A" to setOf("tag"), "service-C" to setOf("tag")) ) @@ -101,7 +94,7 @@ class EnvoyClusterFactoryTest { } @Test - fun `should return clusters from tag dependency with `() { + fun `should return clusters from tag dependency with keeping order`() { // given val properties = SnapshotProperties() val factory = EnvoyClustersFactory(properties) @@ -115,12 +108,9 @@ class EnvoyClusterFactoryTest { ) ) val services = listOf("service-A", "service-B", "service-C") - val globalSnapshot = GlobalSnapshot( - clusters = createClusters(properties, services), - allServicesNames = services.toSet(), - endpoints = emptyMap(), - clusterConfigurations = emptyMap(), - securedClusters = emptyMap(), + val globalSnapshot = buildGlobalSnapshot( + services = services, + properties = properties, tags = mapOf("service-A" to setOf("tag-1"), "service-C" to setOf("tag-1", "tag-2"), "service-B" to setOf("tag-2")) ) @@ -153,19 +143,16 @@ class EnvoyClusterFactoryTest { serviceDependency("service-A", 44) ), tagDependencies = listOf( - tagDependency("tag-1", 33) + tagDependency("tag", 33) ) ) ) ) val services = listOf("service-A", "service-B", "service-C") - val globalSnapshot = GlobalSnapshot( - clusters = createClusters(properties, services), - allServicesNames = services.toSet(), - endpoints = emptyMap(), - clusterConfigurations = emptyMap(), - securedClusters = emptyMap(), - tags = mapOf("service-A" to setOf("tag-1"), "service-C" to setOf("tag-1")) + val globalSnapshot = buildGlobalSnapshot( + services = services, + properties = properties, + tags = mapOf("service-A" to setOf("tag"), "service-C" to setOf("tag")) ) // when @@ -189,13 +176,9 @@ class EnvoyClusterFactoryTest { val factory = EnvoyClustersFactory(properties) val group = allServicesGroup val services = listOf("service-A", "service-B", "service-C") - val globalSnapshot = GlobalSnapshot( - clusters = createClusters(properties, services), - allServicesNames = services.toSet(), - endpoints = emptyMap(), - clusterConfigurations = emptyMap(), - securedClusters = emptyMap(), - tags = emptyMap() + val globalSnapshot = buildGlobalSnapshot( + services = services, + properties = properties ) // when @@ -222,13 +205,9 @@ class EnvoyClusterFactoryTest { ) ) val services = listOf("service-A", "service-B", "service-C") - val globalSnapshot = GlobalSnapshot( - clusters = createClusters(properties, services), - allServicesNames = services.toSet(), - endpoints = emptyMap(), - clusterConfigurations = emptyMap(), - securedClusters = emptyMap(), - tags = emptyMap() + val globalSnapshot = buildGlobalSnapshot( + services = services, + properties = properties ) // when @@ -257,14 +236,10 @@ class EnvoyClusterFactoryTest { ) ) val services = listOf("service-A", "service-B", "service-C") - val globalSnapshot = GlobalSnapshot( - clusters = createClusters(properties, services), - allServicesNames = services.toSet(), - endpoints = emptyMap(), - clusterConfigurations = emptyMap(), - securedClusters = emptyMap(), - tags = mapOf("service-A" to setOf("tag")) - ) + val globalSnapshot = buildGlobalSnapshot( + services = services, + properties = properties, + tags = mapOf("service-A" to setOf("tag"))) // when val clustersForGroup = factory.getClustersForGroup(group, globalSnapshot) @@ -279,6 +254,19 @@ class EnvoyClusterFactoryTest { } } +private fun buildGlobalSnapshot( + services: Collection = emptyList(), + properties: SnapshotProperties = SnapshotProperties(), + tags: Map> = emptyMap() +) = GlobalSnapshot( + clusters = createClusters(properties, services.toList()), + allServicesNames = services.toSet(), + endpoints = emptyMap(), + clusterConfigurations = emptyMap(), + securedClusters = emptyMap(), + tags = tags +) + private fun List.assertServiceCluster(name: String): ObjectAssert { return assertThat(this) .filteredOn { it.name == name } diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt index 6ef105586..93c3fb517 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt @@ -35,7 +35,7 @@ class EnvoyListenersFactoryTest { } @Test - fun `should return egress http proxy virtual listener listener with service dependency`() { + fun `should return egress http proxy virtual listener with service dependency`() { // given val properties = SnapshotProperties() val factory = EnvoyListenersFactory(properties, EnvoyHttpFilters(emptyList(), emptyList())) @@ -66,7 +66,7 @@ class EnvoyListenersFactoryTest { } @Test - fun `should return egress http proxy virtual listener listener with tag dependency`() { + fun `should return egress http proxy virtual listener with tag dependency`() { // given val properties = SnapshotProperties() val factory = EnvoyListenersFactory(properties, EnvoyHttpFilters(emptyList(), emptyList())) diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/routes/EnvoyEgressRoutesFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/routes/EnvoyEgressRoutesFactoryTest.kt index 7876fada6..52a34b493 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/routes/EnvoyEgressRoutesFactoryTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/routes/EnvoyEgressRoutesFactoryTest.kt @@ -13,6 +13,8 @@ import pl.allegro.tech.servicemesh.envoycontrol.groups.hasRequestHeadersToRemove import pl.allegro.tech.servicemesh.envoycontrol.groups.hasResponseHeaderToAdd import pl.allegro.tech.servicemesh.envoycontrol.groups.hasVirtualHostsInOrder import pl.allegro.tech.servicemesh.envoycontrol.groups.hostRewriteHeaderIsEmpty +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.EgressProperties +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.IncomingPermissionsProperties import pl.allegro.tech.servicemesh.envoycontrol.snapshot.RouteSpecification import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotProperties @@ -36,9 +38,12 @@ internal class EnvoyEgressRoutesFactoryTest { @Test fun `should add client identity header if incoming permissions are enabled`() { // given - val routesFactory = EnvoyEgressRoutesFactory(SnapshotProperties().apply { - incomingPermissions.enabled = true - }) + val routesFactory = EnvoyEgressRoutesFactory( + EgressProperties(), + IncomingPermissionsProperties().apply { + enabled = true + } + ) // when val routeConfig = routesFactory.createEgressRouteConfig("client1", clusters, false) @@ -58,9 +63,12 @@ internal class EnvoyEgressRoutesFactoryTest { @Test fun `should not add client identity header if incoming permissions are disabled`() { // given - val routesFactory = EnvoyEgressRoutesFactory(SnapshotProperties().apply { - incomingPermissions.enabled = false - }) + val routesFactory = EnvoyEgressRoutesFactory( + EgressProperties(), + IncomingPermissionsProperties().apply { + enabled = false + } + ) // when val routeConfig = routesFactory.createEgressRouteConfig("client1", clusters, false) @@ -80,7 +88,7 @@ internal class EnvoyEgressRoutesFactoryTest { @Test fun `should add upstream remote address header if addUpstreamAddress is enabled`() { // given - val routesFactory = EnvoyEgressRoutesFactory(SnapshotProperties()) + val routesFactory = EnvoyEgressRoutesFactory(EgressProperties(), IncomingPermissionsProperties()) // when val routeConfig = routesFactory.createEgressRouteConfig("client1", clusters, true) @@ -93,7 +101,7 @@ internal class EnvoyEgressRoutesFactoryTest { @Test fun `should not add upstream remote address header if addUpstreamAddress is disabled`() { // given - val routesFactory = EnvoyEgressRoutesFactory(SnapshotProperties()) + val routesFactory = EnvoyEgressRoutesFactory(EgressProperties(), IncomingPermissionsProperties()) // when val routeConfig = routesFactory.createEgressRouteConfig("client1", clusters, false) @@ -106,9 +114,12 @@ internal class EnvoyEgressRoutesFactoryTest { @Test fun `should not add auto rewrite host header when feature is disabled in configuration`() { // given - val routesFactory = EnvoyEgressRoutesFactory(SnapshotProperties().apply { - egress.hostHeaderRewriting.enabled = false - }) + val routesFactory = EnvoyEgressRoutesFactory( + EgressProperties().apply { + hostHeaderRewriting.enabled = false + }, + IncomingPermissionsProperties() + ) // when val routeConfig = routesFactory.createEgressRouteConfig("client1", clusters, false) @@ -128,7 +139,7 @@ internal class EnvoyEgressRoutesFactoryTest { egress.hostHeaderRewriting.enabled = true egress.hostHeaderRewriting.customHostHeader = "test_header" } - val routesFactory = EnvoyEgressRoutesFactory(snapshotProperties) + val routesFactory = EnvoyEgressRoutesFactory(snapshotProperties.egress, snapshotProperties.incomingPermissions) // when val routeConfig = routesFactory.createEgressRouteConfig("client1", clusters, false) @@ -144,9 +155,12 @@ internal class EnvoyEgressRoutesFactoryTest { @Test fun `should create route config with headers to remove`() { // given - val routesFactory = EnvoyEgressRoutesFactory(SnapshotProperties().apply { - egress.headersToRemove = mutableListOf("x-special-case-header", "x-custom") - }) + val routesFactory = EnvoyEgressRoutesFactory( + EgressProperties().apply { + headersToRemove = mutableListOf("x-special-case-header", "x-custom") + }, + IncomingPermissionsProperties() + ) // when val routeConfig = routesFactory.createEgressRouteConfig("client1", clusters, false) @@ -158,9 +172,12 @@ internal class EnvoyEgressRoutesFactoryTest { @Test fun `should create route config for domains`() { // given - val routesFactory = EnvoyEgressRoutesFactory(SnapshotProperties().apply { - egress.headersToRemove = mutableListOf("x-special-case-header", "x-custom") - }) + val routesFactory = EnvoyEgressRoutesFactory( + EgressProperties().apply { + headersToRemove = mutableListOf("x-special-case-header", "x-custom") + }, + IncomingPermissionsProperties() + ) val routesSpecifications = listOf( RouteSpecification("example_pl_1553", listOf("example.pl:1553"), DependencySettings()), RouteSpecification("example_com_1553", listOf("example.com:1553"), DependencySettings()) From 85628849b0c8a2006eb0b10dc8ae16cca31c7909 Mon Sep 17 00:00:00 2001 From: Kamil Smigielski Date: Tue, 27 Dec 2022 09:37:51 +0100 Subject: [PATCH 10/15] cr fixes --- .../snapshot/EnvoySnapshotFactory.kt | 9 ++-- .../envoycontrol/snapshot/GlobalSnapshot.kt | 26 ++++++------ .../resource/clusters/EnvoyClustersFactory.kt | 7 ++-- .../snapshot/EnvoySnapshotFactoryTest.kt | 42 +++++++++++++------ .../clusters/EnvoyClusterFactoryTest.kt | 22 ++++++++-- .../listeners/EnvoyListenersFactoryTest.kt | 6 ++- 6 files changed, 76 insertions(+), 36 deletions(-) diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt index cb23eda78..2173588a3 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt @@ -330,11 +330,12 @@ class RouteSpecificationFactory( } val servicesNames = group.proxySettings.outgoing.getServiceDependencies().map { it.service }.toSet() val definedTagsRoutes = globalSnapshot - .getTagsForDependency(group.proxySettings.outgoing) { servicesName, tagDependency -> + .getTagsForDependency(group.proxySettings.outgoing) + .map { RouteSpecification( - clusterName = servicesName, - routeDomains = listOf(servicesName) + getServiceWithCustomDomain(servicesName), - settings = tagDependency.settings + clusterName = it.first, + routeDomains = listOf(it.first) + getServiceWithCustomDomain(it.first), + settings = it.second.settings ) }.distinctBy { it.clusterName } diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/GlobalSnapshot.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/GlobalSnapshot.kt index d65f19662..896d09515 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/GlobalSnapshot.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/GlobalSnapshot.kt @@ -6,22 +6,24 @@ import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment import pl.allegro.tech.servicemesh.envoycontrol.groups.Outgoing import pl.allegro.tech.servicemesh.envoycontrol.groups.TagDependency +typealias ClusterName = String + data class GlobalSnapshot( - val clusters: Map, - val allServicesNames: Set, - val endpoints: Map, - val clusterConfigurations: Map, - val securedClusters: Map, - val tags: Map> + val clusters: Map, + val allServicesNames: Set, + val endpoints: Map, + val clusterConfigurations: Map, + val securedClusters: Map, + val tags: Map> ) { - fun getTagsForDependency( - outgoing: Outgoing, - mapper: (String, TagDependency) -> T): List { + fun getTagsForDependency( + outgoing: Outgoing + ): List> { val serviceDependencies = outgoing.getServiceDependencies().map { it.service }.toSet() return outgoing.getTagDependencies().flatMap { tagDependency -> tags.filterKeys { !serviceDependencies.contains(it) } .filterValues { it.contains(tagDependency.tag) } - .map { mapper(it.key, tagDependency) } + .map { it.key to tagDependency } } } } @@ -31,9 +33,9 @@ fun globalSnapshot( clusters: Iterable = emptyList(), endpoints: Iterable = emptyList(), properties: OutgoingPermissionsProperties = OutgoingPermissionsProperties(), - clusterConfigurations: Map = emptyMap(), + clusterConfigurations: Map = emptyMap(), securedClusters: List = emptyList(), - tags: Map> + tags: Map> ): GlobalSnapshot { val clusters = SnapshotResources.create(clusters, "").resources() val securedClusters = SnapshotResources.create(securedClusters, "").resources() diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt index 08e14f688..348985f1b 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt @@ -195,9 +195,10 @@ class EnvoyClustersFactory( .associateBy({ it.service }, { it.settings }) val serviceFromTagSettings = globalSnapshot - .getTagsForDependency(group.proxySettings.outgoing) { serviceName, tagDependency -> - serviceName to tagDependency.settings - }.reversed().associateBy({ it.first }, { it.second }) + .getTagsForDependency(group.proxySettings.outgoing) + .map { it.first to it.second.settings } + .distinctBy { it.first } + .associateBy({ it.first }, { it.second }) val clustersForGroup = when (group) { is ServicesGroup -> serviceSettings.mapNotNull { diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt index b7e64fc26..1aef94a28 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt @@ -16,8 +16,12 @@ class EnvoySnapshotFactoryTest { fun `should return all tags when prefix is empty`() { // given val tagPrefix = "" - val serviceTags = mapOf("abc" to setOf("uws", "poc"), "xyz" to setOf("uj"), "qwerty" to setOf()) - val state: MultiClusterState = MultiClusterState(listOf( + val serviceTags = mapOf( + serviceWithTags("abc", "uws", "poc"), + serviceWithTags("xyz", "uj"), + serviceWithTags("qwerty") + ) + val state = MultiClusterState(listOf( ClusterState(serviceState(serviceTags), Locality.LOCAL, "cluster") )) @@ -31,8 +35,12 @@ class EnvoySnapshotFactoryTest { @Test fun `should return all tags with prefix`() { val tagPrefix = "tag:" - val serviceTags = mapOf("abc" to setOf("tag:uws", "poc"), "xyz" to setOf("uj"), "qwerty" to setOf()) - val state: MultiClusterState = MultiClusterState(listOf( + val serviceTags = mapOf( + serviceWithTags("abc", "tag:uws", "poc"), + serviceWithTags("xyz", "uj"), + serviceWithTags("qwerty") + ) + val state = MultiClusterState(listOf( ClusterState(serviceState(serviceTags), Locality.LOCAL, "cluster") )) @@ -41,9 +49,9 @@ class EnvoySnapshotFactoryTest { // then assertThat(tags).isEqualTo(mapOf( - "abc" to setOf("tag:uws"), - "xyz" to emptySet(), - "qwerty" to emptySet() + serviceWithTags("abc", "tag:uws"), + serviceWithTags("xyz"), + serviceWithTags("qwerty") )) } @@ -51,8 +59,14 @@ class EnvoySnapshotFactoryTest { fun `should merge multiple Cluster State`() { // given val tagPrefix = "" - val serviceTagsCluster1 = mapOf("abc" to setOf("uws", "poc"), "xyz" to setOf("uj"), "qwerty" to setOf()) - val serviceTagsCluster2 = mapOf("abc" to setOf("lkj"), "xyz" to setOf(), "qwerty" to setOf("ban")) + val serviceTagsCluster1 = mapOf( + serviceWithTags("abc", "uws", "poc"), + serviceWithTags("xyz", "uj"), + serviceWithTags("qwerty")) + val serviceTagsCluster2 = mapOf( + serviceWithTags("abc", "lkj"), + serviceWithTags("xyz"), + serviceWithTags("qwerty", "ban")) val state = MultiClusterState(listOf( ClusterState(serviceState(serviceTagsCluster1), Locality.LOCAL, "cluster"), ClusterState(serviceState(serviceTagsCluster2), Locality.LOCAL, "cluster2") @@ -63,9 +77,9 @@ class EnvoySnapshotFactoryTest { // then assertThat(tags).isEqualTo(mapOf( - "abc" to setOf("uws", "poc", "lkj"), - "xyz" to setOf("uj"), - "qwerty" to setOf("ban") + serviceWithTags("abc", "uws", "poc", "lkj"), + serviceWithTags("xyz", "uj"), + serviceWithTags("qwerty", "ban") )) } @@ -76,3 +90,7 @@ class EnvoySnapshotFactoryTest { return ServicesState(servicesInstances) } } + +private fun serviceWithTags(serviceName: String, vararg tags: String): Pair> { + return serviceName to tags.toSet() +} diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt index 276d4bebb..8271b7ae0 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt @@ -76,7 +76,10 @@ class EnvoyClusterFactoryTest { val globalSnapshot = buildGlobalSnapshot( services = services, properties = properties, - tags = mapOf("service-A" to setOf("tag"), "service-C" to setOf("tag")) + tags = mapOf( + serviceWithTags("service-A", "tag"), + serviceWithTags("service-C", "tag") + ) ) // when @@ -111,7 +114,11 @@ class EnvoyClusterFactoryTest { val globalSnapshot = buildGlobalSnapshot( services = services, properties = properties, - tags = mapOf("service-A" to setOf("tag-1"), "service-C" to setOf("tag-1", "tag-2"), "service-B" to setOf("tag-2")) + tags = mapOf( + serviceWithTags("service-A","tag-1"), + serviceWithTags("service-C","tag-1", "tag-2"), + serviceWithTags("service-B","tag-2") + ) ) // when @@ -152,7 +159,10 @@ class EnvoyClusterFactoryTest { val globalSnapshot = buildGlobalSnapshot( services = services, properties = properties, - tags = mapOf("service-A" to setOf("tag"), "service-C" to setOf("tag")) + tags = mapOf( + serviceWithTags("service-A", "tag"), + serviceWithTags("service-C", "tag") + ) ) // when @@ -239,7 +249,7 @@ class EnvoyClusterFactoryTest { val globalSnapshot = buildGlobalSnapshot( services = services, properties = properties, - tags = mapOf("service-A" to setOf("tag"))) + tags = mapOf(serviceWithTags("service-A", "tag"))) // when val clustersForGroup = factory.getClustersForGroup(group, globalSnapshot) @@ -279,3 +289,7 @@ private fun ObjectAssert.hasIdleTimeout(idleTimeout: Long): ObjectAsser .isEqualTo(idleTimeout) return this } + +private fun serviceWithTags(serviceName: String, vararg tags: String): Pair> { + return serviceName to tags.toSet() +} diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt index 93c3fb517..5835c6618 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactoryTest.kt @@ -84,7 +84,7 @@ class EnvoyListenersFactoryTest { endpoints = emptyMap(), clusterConfigurations = emptyMap(), securedClusters = emptyMap(), - tags = mapOf("service-B" to setOf("tag")) + tags = mapOf(serviceWithTags("service-B", "tag")) ) // when @@ -96,3 +96,7 @@ class EnvoyListenersFactoryTest { .contains("0.0.0.0:80") } } + +private fun serviceWithTags(serviceName: String, vararg tags: String): Pair> { + return serviceName to tags.toSet() +} From 42be32283c5cdd24a854a5870968c44e0f75bace Mon Sep 17 00:00:00 2001 From: Kamil Smigielski Date: Wed, 28 Dec 2022 11:19:56 +0100 Subject: [PATCH 11/15] fix lint --- .../snapshot/resource/clusters/EnvoyClusterFactoryTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt index 8271b7ae0..bce35b17e 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt @@ -115,9 +115,9 @@ class EnvoyClusterFactoryTest { services = services, properties = properties, tags = mapOf( - serviceWithTags("service-A","tag-1"), - serviceWithTags("service-C","tag-1", "tag-2"), - serviceWithTags("service-B","tag-2") + serviceWithTags("service-A", "tag-1"), + serviceWithTags("service-C", "tag-1", "tag-2"), + serviceWithTags("service-B", "tag-2") ) ) From 554147ace42cd0aba00b95f43224c2fa5eecc40f Mon Sep 17 00:00:00 2001 From: Kamil Smigielski Date: Thu, 29 Dec 2022 09:26:42 +0100 Subject: [PATCH 12/15] cr --- .../envoycontrol/snapshot/EnvoySnapshotFactory.kt | 6 +++--- .../envoycontrol/snapshot/GlobalSnapshot.kt | 11 ++++++++--- .../resource/clusters/EnvoyClustersFactory.kt | 10 +++++----- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt index 2173588a3..afc765945 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt @@ -333,9 +333,9 @@ class RouteSpecificationFactory( .getTagsForDependency(group.proxySettings.outgoing) .map { RouteSpecification( - clusterName = it.first, - routeDomains = listOf(it.first) + getServiceWithCustomDomain(it.first), - settings = it.second.settings + clusterName = it.clusterName, + routeDomains = listOf(it.clusterName) + getServiceWithCustomDomain(it.clusterName), + settings = it.settings ) }.distinctBy { it.clusterName } diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/GlobalSnapshot.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/GlobalSnapshot.kt index 896d09515..4891930f6 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/GlobalSnapshot.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/GlobalSnapshot.kt @@ -3,8 +3,8 @@ package pl.allegro.tech.servicemesh.envoycontrol.snapshot import io.envoyproxy.controlplane.cache.SnapshotResources import io.envoyproxy.envoy.config.cluster.v3.Cluster import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment +import pl.allegro.tech.servicemesh.envoycontrol.groups.DependencySettings import pl.allegro.tech.servicemesh.envoycontrol.groups.Outgoing -import pl.allegro.tech.servicemesh.envoycontrol.groups.TagDependency typealias ClusterName = String @@ -18,16 +18,21 @@ data class GlobalSnapshot( ) { fun getTagsForDependency( outgoing: Outgoing - ): List> { + ): List { val serviceDependencies = outgoing.getServiceDependencies().map { it.service }.toSet() return outgoing.getTagDependencies().flatMap { tagDependency -> tags.filterKeys { !serviceDependencies.contains(it) } .filterValues { it.contains(tagDependency.tag) } - .map { it.key to tagDependency } + .map { TagDependencySettings(it.key, tagDependency.settings) } } } } +data class TagDependencySettings( + val clusterName: ClusterName, + val settings: DependencySettings +) + @Suppress("LongParameterList") fun globalSnapshot( clusters: Iterable = emptyList(), diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt index 348985f1b..27f4282f6 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClustersFactory.kt @@ -191,14 +191,14 @@ class EnvoyClustersFactory( globalSnapshot.clusters } - val serviceSettings = group.proxySettings.outgoing.getServiceDependencies() - .associateBy({ it.service }, { it.settings }) + val serviceSettings = + group.proxySettings.outgoing.getServiceDependencies() + .associate { it.service to it.settings } val serviceFromTagSettings = globalSnapshot .getTagsForDependency(group.proxySettings.outgoing) - .map { it.first to it.second.settings } - .distinctBy { it.first } - .associateBy({ it.first }, { it.second }) + .distinctBy { it.clusterName } + .associate { it.clusterName to it.settings } val clustersForGroup = when (group) { is ServicesGroup -> serviceSettings.mapNotNull { From 0ad6bfc40a4862328f6bb532486cfd7d6fff42c8 Mon Sep 17 00:00:00 2001 From: Kamil Smigielski Date: Wed, 4 Jan 2023 08:48:54 +0100 Subject: [PATCH 13/15] cr --- .../snapshot/EnvoySnapshotFactory.kt | 4 +-- .../listeners/EnvoyListenersFactory.kt | 4 +-- .../snapshot/EnvoySnapshotFactoryTest.kt | 6 ++-- .../clusters/EnvoyClusterFactoryTest.kt | 28 ++++++++++++++++++- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt index afc765945..580811522 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactory.kt @@ -40,7 +40,7 @@ class EnvoySnapshotFactory( companion object { const val DEFAULT_HTTP_PORT = 80 - internal fun tagExtractor(tagPrefix: String, servicesStates: MultiClusterState): Map> = + internal fun extractTags(tagPrefix: String, servicesStates: MultiClusterState): Map> = servicesStates.flatMap { it.servicesState.serviceNameToInstances.asIterable() } .fold(emptyMap()) { acc, entry -> @@ -76,7 +76,7 @@ class EnvoySnapshotFactory( securedClusters = securedClusters, endpoints = endpoints, properties = properties.outgoingPermissions, - tags = tagExtractor(properties.outgoingPermissions.tagPrefix, servicesStates) + tags = extractTags(properties.outgoingPermissions.tagPrefix, servicesStates) ) sample.stop(meterRegistry.timer("snapshot-factory.new-snapshot.time")) diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactory.kt index a88e52f26..ccb27659a 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/EnvoyListenersFactory.kt @@ -290,11 +290,11 @@ class EnvoyListenersFactory( } private fun createEgressHttpProxyVirtualListener( - portAndDomains: Set, + ports: Set, group: Group, globalSnapshot: GlobalSnapshot ): List { - return portAndDomains.map { port -> + return ports.map { port -> Listener.newBuilder() .setName("$DOMAIN_PROXY_LISTENER_ADDRESS:$port") .setAddress( diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt index 1aef94a28..22cdd9712 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/EnvoySnapshotFactoryTest.kt @@ -26,7 +26,7 @@ class EnvoySnapshotFactoryTest { )) // when - val tags = EnvoySnapshotFactory.tagExtractor(tagPrefix, state) + val tags = EnvoySnapshotFactory.extractTags(tagPrefix, state) // then assertThat(tags).isEqualTo(serviceTags) @@ -45,7 +45,7 @@ class EnvoySnapshotFactoryTest { )) // when - val tags = EnvoySnapshotFactory.tagExtractor(tagPrefix, state) + val tags = EnvoySnapshotFactory.extractTags(tagPrefix, state) // then assertThat(tags).isEqualTo(mapOf( @@ -73,7 +73,7 @@ class EnvoySnapshotFactoryTest { )) // when - val tags = EnvoySnapshotFactory.tagExtractor(tagPrefix, state) + val tags = EnvoySnapshotFactory.extractTags(tagPrefix, state) // then assertThat(tags).isEqualTo(mapOf( diff --git a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt index bce35b17e..006848c14 100644 --- a/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt +++ b/envoy-control-core/src/test/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/clusters/EnvoyClusterFactoryTest.kt @@ -31,6 +31,32 @@ class EnvoyClusterFactoryTest { ) } + @Test + fun `should not return cluster from service dependency when is not present in global snapshot`() { + // given + val properties = SnapshotProperties() + val factory = EnvoyClustersFactory(properties) + val group = serviceGroup.copy( + proxySettings = ProxySettings( + outgoing = Outgoing( + serviceDependencies = listOf(serviceDependency("service-A", 33)) + ) + ) + ) + val services = listOf("service-B", "service-C") + val globalSnapshot = buildGlobalSnapshot( + services = services, + properties = properties + ) + + // when + val clustersForGroup = factory.getClustersForGroup(group, globalSnapshot) + + // then + assertThat(clustersForGroup) + .isEmpty() + } + @Test fun `should return cluster from service dependency`() { // given @@ -139,7 +165,7 @@ class EnvoyClusterFactoryTest { } @Test - fun `should return correct configuration for clusters from tag dependency where one service has multiple tags`() { + fun `should return correct configuration for clusters from service dependency when service has tag`() { // given val properties = SnapshotProperties() val factory = EnvoyClustersFactory(properties) From ddd34ddb47fbd5fe2d4e6a3c5ec5b034055de710 Mon Sep 17 00:00:00 2001 From: "radoslaw.chrzanowski" Date: Wed, 18 Jan 2023 09:19:25 +0100 Subject: [PATCH 14/15] Fix flaky test --- .../envoycontrol/EndpointMetadataMergingTests.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/EndpointMetadataMergingTests.kt b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/EndpointMetadataMergingTests.kt index 98eb5b8cc..885ebc914 100644 --- a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/EndpointMetadataMergingTests.kt +++ b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/EndpointMetadataMergingTests.kt @@ -9,6 +9,7 @@ import pl.allegro.tech.servicemesh.envoycontrol.config.envoy.CallStats import pl.allegro.tech.servicemesh.envoycontrol.config.envoy.EnvoyExtension import pl.allegro.tech.servicemesh.envoycontrol.config.envoycontrol.EnvoyControlExtension import pl.allegro.tech.servicemesh.envoycontrol.config.service.EchoServiceExtension +import java.time.Duration open class EndpointMetadataMergingTests { @@ -52,9 +53,11 @@ open class EndpointMetadataMergingTests { val dolomStats = callEchoServiceRepeatedly(service, repeat = 1, tag = "dolom") // then - assertThat(ipsumStats.hits(service)).isEqualTo(1) - assertThat(loremStats.hits(service)).isEqualTo(1) - assertThat(dolomStats.hits(service)).isEqualTo(1) + untilAsserted(wait = Duration.ofSeconds(30)) { + assertThat(ipsumStats.hits(service)).isEqualTo(1) + assertThat(loremStats.hits(service)).isEqualTo(1) + assertThat(dolomStats.hits(service)).isEqualTo(1) + } } protected open fun callEchoServiceRepeatedly( From 95ae0afe68e56f71db777b529afe2536d1032119 Mon Sep 17 00:00:00 2001 From: Kamil Smigielski Date: Wed, 18 Jan 2023 11:22:54 +0100 Subject: [PATCH 15/15] fix lint --- .../servicemesh/envoycontrol/EndpointMetadataMergingTests.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/EndpointMetadataMergingTests.kt b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/EndpointMetadataMergingTests.kt index 885ebc914..02004e2c3 100644 --- a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/EndpointMetadataMergingTests.kt +++ b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/EndpointMetadataMergingTests.kt @@ -36,6 +36,7 @@ open class EndpointMetadataMergingTests { } @Test + @Suppress("MagicNumber") fun `should merge all service tags of endpoints with the same ip and port`() { // given consul.server.operations.registerService(name = "echo", extension = service, tags = listOf("ipsum"))