From d141457abb7ae1b571254b29476f3778344bf948 Mon Sep 17 00:00:00 2001 From: Chris Gresty Date: Wed, 6 Nov 2019 09:15:59 +0000 Subject: [PATCH 1/2] Expose styx origins config file in Admin interface --- .../styx/admin/handlers/TextHttpHandler.java | 57 +++++++++++++++ .../services/YamlFileConfigurationService.kt | 9 +++ .../admin/OriginsFileCompatibilitySpec.kt | 70 +++++++++++++++++-- 3 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 components/proxy/src/main/java/com/hotels/styx/admin/handlers/TextHttpHandler.java diff --git a/components/proxy/src/main/java/com/hotels/styx/admin/handlers/TextHttpHandler.java b/components/proxy/src/main/java/com/hotels/styx/admin/handlers/TextHttpHandler.java new file mode 100644 index 0000000000..3c988300a0 --- /dev/null +++ b/components/proxy/src/main/java/com/hotels/styx/admin/handlers/TextHttpHandler.java @@ -0,0 +1,57 @@ +/* + Copyright (C) 2013-2019 Expedia Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +package com.hotels.styx.admin.handlers; + +import com.hotels.styx.api.ByteStream; +import com.hotels.styx.api.Eventual; +import com.hotels.styx.api.HttpHandler; +import com.hotels.styx.api.HttpInterceptor; +import com.hotels.styx.api.LiveHttpRequest; +import com.hotels.styx.api.LiveHttpResponse; + +import java.util.function.Supplier; + +import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8; +import static com.hotels.styx.api.HttpHeaderNames.CONTENT_TYPE; +import static com.hotels.styx.api.HttpResponseStatus.OK; +import static com.hotels.styx.api.LiveHttpResponse.response; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; + +/** + * Responds with UTF8 text stream. + */ +public class TextHttpHandler implements HttpHandler { + + private final Supplier text; + + public TextHttpHandler(Supplier text) { + this.text = requireNonNull(text, "text supplier cannot be null"); + } + + @Override + public Eventual handle(LiveHttpRequest request, HttpInterceptor.Context context) { + return Eventual.of(createResponse()); + } + + protected LiveHttpResponse createResponse() { + return response(OK) + .disableCaching() + .addHeader(CONTENT_TYPE, PLAIN_TEXT_UTF_8) + .body(ByteStream.from(text.get(), UTF_8)) + .build(); + } +} diff --git a/components/proxy/src/main/kotlin/com/hotels/styx/services/YamlFileConfigurationService.kt b/components/proxy/src/main/kotlin/com/hotels/styx/services/YamlFileConfigurationService.kt index 941e6fc97e..abe984aebb 100644 --- a/components/proxy/src/main/kotlin/com/hotels/styx/services/YamlFileConfigurationService.kt +++ b/components/proxy/src/main/kotlin/com/hotels/styx/services/YamlFileConfigurationService.kt @@ -16,6 +16,7 @@ package com.hotels.styx.services import com.fasterxml.jackson.databind.JsonNode +import com.hotels.styx.admin.handlers.TextHttpHandler import com.hotels.styx.api.extension.service.spi.StyxService import com.hotels.styx.config.schema.SchemaDsl import com.hotels.styx.config.schema.SchemaDsl.bool @@ -56,6 +57,9 @@ internal class YamlFileConfigurationService( private val healthMonitors = AtomicReference>>(listOf()) + @Volatile + private var originsConfig = "" + companion object { @JvmField val SCHEMA = SchemaDsl.`object`( @@ -78,6 +82,10 @@ internal class YamlFileConfigurationService( LOGGER.info("service stopped") } + override fun adminInterfaceHandlers() = + mutableMapOf(Pair("origins", TextHttpHandler { originsConfig })) + + fun reloadAction(content: String): Unit { LOGGER.info("New origins configuration: \n$content") @@ -91,6 +99,7 @@ internal class YamlFileConfigurationService( }.mapCatching { (healthMonitors, routingObjectDefs) -> updateRoutingObjects(routingObjectDefs) updateHealthCheckServices(serviceDb, healthMonitors) + originsConfig = content initialised.countDown() }.onFailure { LOGGER.error("Failed to reload new configuration. cause='{}'", it.message, it) diff --git a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/admin/OriginsFileCompatibilitySpec.kt b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/admin/OriginsFileCompatibilitySpec.kt index b91f61bef3..233b9faf32 100644 --- a/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/admin/OriginsFileCompatibilitySpec.kt +++ b/system-tests/ft-suite/src/test/kotlin/com/hotels/styx/admin/OriginsFileCompatibilitySpec.kt @@ -82,12 +82,13 @@ class OriginsFileCompatibilitySpec : FunSpec() { init { context("Styx server starts") { - writeOrigins(""" + val originsFile = """ - id: appA path: "/" origins: - { id: "appA-01", host: "localhost:${mockServerA01.port()}" } - """.trimIndent()) + """.trimIndent() + writeOrigins(originsFile) styxServer.restart() test("It populates forwarding path from the origins yaml file") { @@ -102,6 +103,17 @@ class OriginsFileCompatibilitySpec : FunSpec() { } } } + + test("The origins config file is returned from the admin service") { + client.send(get("/admin/providers/originsFileLoader/origins") + .header(HOST, styxServer().adminHostHeader()) + .build()) + .wait()!! + .let { + it.status() shouldBe OK + it.bodyAs(UTF_8) shouldBe originsFile + } + } } context("Origins configuration changes") { @@ -419,6 +431,14 @@ class OriginsFileCompatibilitySpec : FunSpec() { } context("Admin interface") { + + val validOriginsFile = """ + - id: appA + path: "/" + origins: + - { id: "appA-01", host: "localhost:${mockServerA01.port()}" } + """.trimIndent() + test("Styx dashboard is disabled") { client.send(get("/") .header(HOST, styxServer().adminHostHeader()) @@ -447,6 +467,42 @@ class OriginsFileCompatibilitySpec : FunSpec() { it.bodyAs(UTF_8) shouldContain """
  • Configuration
  • """ } } + + test("Modified origins config is returned after update") { + + writeOrigins(validOriginsFile) + + eventually(2.seconds, AssertionError::class.java) { + client.send(get("/admin/providers/originsFileLoader/origins") + .header(HOST, styxServer().adminHostHeader()) + .build()) + .wait()!! + .let { + it.status() shouldBe OK + it.bodyAs(UTF_8) shouldBe validOriginsFile + } + } + } + + test("Original origins config is returned after invalid update") { + + writeOrigins(""" + - id: appA + - this file has somehow corrupted + .. bl;ah blah" + """.trimIndent()) + + eventually(2.seconds, AssertionError::class.java) { + client.send(get("/admin/providers/originsFileLoader/origins") + .header(HOST, styxServer().adminHostHeader()) + .build()) + .wait()!! + .let { + it.status() shouldBe OK + it.bodyAs(UTF_8) shouldBe validOriginsFile + } + } + } } context("Creates a health checking service") { @@ -592,12 +648,13 @@ class OriginsFileCompatibilitySpec : FunSpec() { context("Error scenarios") { - writeOrigins(""" + val originsFile = """ - id: appA path: "/" origins: - { id: "appA-01", host: "localhost:${mockServerA01.port()}" } - """.trimIndent()) + """.trimIndent() + writeOrigins(originsFile) styxServer.restart() test("Keeps the original configuration when a one has problems") { @@ -628,12 +685,13 @@ class OriginsFileCompatibilitySpec : FunSpec() { } test("Reloads a new configuration after error") { - writeOrigins(""" + val newOriginsFile = """ - id: appA path: "/" origins: - { id: "appA-02", host: "localhost:${mockServerA02.port()}" } - """.trimIndent()) + """.trimIndent() + writeOrigins(newOriginsFile) eventually(2.seconds, AssertionError::class.java) { client.send(get("/20") From 02692453f6066594e52adc484476336e456fc8b2 Mon Sep 17 00:00:00 2001 From: Chris Gresty Date: Wed, 6 Nov 2019 13:49:26 +0000 Subject: [PATCH 2/2] Refinements after review --- .../com/hotels/styx/services/YamlFileConfigurationService.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/proxy/src/main/kotlin/com/hotels/styx/services/YamlFileConfigurationService.kt b/components/proxy/src/main/kotlin/com/hotels/styx/services/YamlFileConfigurationService.kt index abe984aebb..616ca5a6bb 100644 --- a/components/proxy/src/main/kotlin/com/hotels/styx/services/YamlFileConfigurationService.kt +++ b/components/proxy/src/main/kotlin/com/hotels/styx/services/YamlFileConfigurationService.kt @@ -83,7 +83,7 @@ internal class YamlFileConfigurationService( } override fun adminInterfaceHandlers() = - mutableMapOf(Pair("origins", TextHttpHandler { originsConfig })) + mapOf("origins" to TextHttpHandler { originsConfig }) fun reloadAction(content: String): Unit { @@ -99,6 +99,7 @@ internal class YamlFileConfigurationService( }.mapCatching { (healthMonitors, routingObjectDefs) -> updateRoutingObjects(routingObjectDefs) updateHealthCheckServices(serviceDb, healthMonitors) + }.onSuccess { originsConfig = content initialised.countDown() }.onFailure {