Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose provider admin interface endpoints #515

Merged
merged 3 commits into from
Nov 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
package com.hotels.styx.admin;

import com.codahale.metrics.json.MetricsModule;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.ImmutableList;
import com.hotels.styx.Environment;
import com.hotels.styx.StartupConfig;
import com.hotels.styx.StyxConfig;
Expand Down Expand Up @@ -46,6 +46,7 @@
import com.hotels.styx.api.configuration.Configuration;
import com.hotels.styx.api.extension.service.BackendService;
import com.hotels.styx.api.extension.service.spi.Registry;
import com.hotels.styx.api.extension.service.spi.StyxService;
import com.hotels.styx.common.http.handler.HttpAggregator;
import com.hotels.styx.common.http.handler.HttpMethodFilteringHandler;
import com.hotels.styx.common.http.handler.StaticBodyHttpHandler;
Expand Down Expand Up @@ -73,6 +74,8 @@
import static com.google.common.net.MediaType.HTML_UTF_8;
import static com.hotels.styx.admin.handlers.IndexHandler.Link.link;
import static com.hotels.styx.api.HttpMethod.POST;
import static com.hotels.styx.routing.config.ConfigVersionResolver.Version.ROUTING_CONFIG_V1;
import static com.hotels.styx.routing.config.ConfigVersionResolver.configVersion;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
Expand Down Expand Up @@ -125,9 +128,9 @@ private HttpHandler adminEndpoints(StyxConfig styxConfig, StartupConfig startupC
Optional<Duration> metricsCacheExpiration = styxConfig.adminServerConfig().metricsCacheExpiration();

AdminHttpRouter httpRouter = new AdminHttpRouter();
httpRouter.aggregate("/", new IndexHandler(indexLinkPaths()));
httpRouter.aggregate("/", new IndexHandler(indexLinkPaths(styxConfig)));
httpRouter.aggregate("/version.txt", new VersionTextHandler(styxConfig.versionFiles(startupConfig)));
httpRouter.aggregate("/admin", new IndexHandler(indexLinkPaths()));
httpRouter.aggregate("/admin", new IndexHandler(indexLinkPaths(styxConfig)));
httpRouter.aggregate("/admin/ping", new PingHandler());
httpRouter.aggregate("/admin/threads", new ThreadsHandler());
httpRouter.aggregate("/admin/current_requests", new CurrentRequestsHandler(CurrentRequestTracker.INSTANCE));
Expand All @@ -149,25 +152,32 @@ private HttpHandler adminEndpoints(StyxConfig styxConfig, StartupConfig startupC
httpRouter.aggregate("/admin/service/providers", serviceProvideHandler);
httpRouter.aggregate("/admin/service/provider/", serviceProvideHandler);

// Dashboard
httpRouter.aggregate("/admin/dashboard/data.json", dashboardDataHandler(styxConfig));
httpRouter.aggregate("/admin/dashboard/", new ClassPathResourceHandler("/admin/dashboard/"));
if (configVersion(styxConfig) == ROUTING_CONFIG_V1) {
httpRouter.aggregate("/admin/dashboard/data.json", dashboardDataHandler(styxConfig));
httpRouter.aggregate("/admin/dashboard/", new ClassPathResourceHandler("/admin/dashboard/"));
}

// Tasks
httpRouter.aggregate("/admin/tasks/origins/reload", new HttpMethodFilteringHandler(POST, new OriginsReloadCommandHandler(backendServicesRegistry)));
httpRouter.aggregate("/admin/tasks/origins", new HttpMethodFilteringHandler(POST, new OriginsCommandHandler(environment.eventBus())));
httpRouter.aggregate("/admin/tasks/plugin/", new PluginToggleHandler(environment.configStore()));

// Plugins Handler

environment.configStore().watchAll("plugins", NamedPlugin.class)
.forEach(entry -> {
NamedPlugin namedPlugin = entry.value();

routesForPlugin(namedPlugin).forEach(route ->
httpRouter.stream(route.path(), route.handler()));
extensionEndpoints("plugins", namedPlugin.name(), namedPlugin.adminInterfaceHandlers())
.forEach(route -> httpRouter.stream(route.path(), route.handler()));
});

providerDatabase.entrySet().forEach(record -> {
String extensionName = record.getKey();
StyxService styxService = record.getValue().component4();

extensionEndpoints("providers", extensionName, styxService.adminInterfaceHandlers())
.forEach(route -> httpRouter.stream(route.path, route.handler()));
});

httpRouter.aggregate("/admin/plugins", new PluginListHandler(environment.configStore()));
return httpRouter;
}
Expand All @@ -178,35 +188,43 @@ private JsonHandler<DashboardData> dashboardDataHandler(StyxConfig styxConfig) {
new MetricsModule(SECONDS, MILLISECONDS, false));
}

private static Iterable<IndexHandler.Link> indexLinkPaths() {
return ImmutableSortedSet.of(
link("version.txt", "/version.txt"),
link("Ping", "/admin/ping"),
link("Threads", "/admin/threads"),
link("Current Requests", "/admin/current_requests?withStackTrace=true"),
link("Metrics", "/admin/metrics?pretty"),
link("Configuration", "/admin/configuration?pretty"),
link("Log Configuration", "/admin/configuration/logging"),
link("Origins Configuration", "/admin/configuration/origins?pretty"),
link("Startup Configuration", "/admin/configuration/startup"),
link("JVM", "/admin/jvm?pretty"),
link("Origins Status", "/admin/origins/status?pretty"),
link("Dashboard", "/admin/dashboard/index.html"),
link("Plugins", "/admin/plugins"));
private static Iterable<IndexHandler.Link> indexLinkPaths(StyxConfig styxConfig) {
ImmutableList.Builder<IndexHandler.Link> builder = ImmutableList.builder();
builder.add(link("Kotlin/Test page", "/admin/kotlin/test"));
builder.add(link("version.txt", "/version.txt"));
builder.add(link("Ping", "/admin/ping"));
builder.add(link("Threads", "/admin/threads"));
builder.add(link("Current Requests", "/admin/current_requests?withStackTrace=true"));
builder.add(link("Metrics", "/admin/metrics?pretty"));
builder.add(link("Configuration", "/admin/configuration?pretty"));
builder.add(link("Log Configuration", "/admin/configuration/logging"));
builder.add(link("Startup Configuration", "/admin/configuration/startup"));
builder.add(link("JVM", "/admin/jvm?pretty"));
builder.add(link("Plugins", "/admin/plugins"));

if (configVersion(styxConfig) == ROUTING_CONFIG_V1) {
builder.add(link("Dashboard", "/admin/dashboard/index.html"))
.add(link("Origins Status", "/admin/origins/status?pretty"))
.add(link("Origins Configuration", "/admin/configuration/origins?pretty"));
}
return builder.build()
.stream()
.sorted()
.collect(toList());
}

private static List<Route> routesForPlugin(NamedPlugin namedPlugin) {
List<PluginAdminEndpointRoute> routes = pluginAdminEndpointRoutes(namedPlugin);
private static List<Route> extensionEndpoints(String root, String name, Map<String, HttpHandler> endpoints) {
List<AdminEndpointRoute> routes = extensionAdminEndpointRoutes(root, name, endpoints);

List<IndexHandler.Link> endpointLinks = routes.stream()
.map(PluginAdminEndpointRoute::link)
.map(AdminEndpointRoute::link)
.collect(toList());

WebServiceHandler handler = endpointLinks.isEmpty()
? new StaticBodyHttpHandler(HTML_UTF_8, format("This plugin (%s) does not expose any admin interfaces", namedPlugin.name()))
? new StaticBodyHttpHandler(HTML_UTF_8, format("This plugin (%s) does not expose any admin interfaces", name))
: new IndexHandler(endpointLinks);

Route indexRoute = new Route(pluginPath(namedPlugin), new HttpAggregator(MEGABYTE, handler));
Route indexRoute = new Route(adminPath(root, name), new HttpAggregator(MEGABYTE, handler));

return concatenate(indexRoute, routes);
}
Expand All @@ -218,15 +236,13 @@ private static <T> List<T> concatenate(T item, List<? extends T> items) {
return list;
}

private static String pluginPath(NamedPlugin namedPlugin) {
return "/admin/plugins/" + namedPlugin.name();
private static String adminPath(String root, String name) {
return String.format("/admin/%s/%s", root, name);
}

private static List<PluginAdminEndpointRoute> pluginAdminEndpointRoutes(NamedPlugin namedPlugin) {
Map<String, HttpHandler> adminInterfaceHandlers = namedPlugin.adminInterfaceHandlers();

return mapToList(adminInterfaceHandlers, (relativePath, handler) ->
new PluginAdminEndpointRoute(namedPlugin, relativePath, handler));
private static List<AdminEndpointRoute> extensionAdminEndpointRoutes(String root, String name, Map<String, HttpHandler> endpoints) {
return mapToList(endpoints, (relativePath, handler) ->
new AdminEndpointRoute(root, name, relativePath, handler));
}

// allows key and value to be labelled in lambda instead of having to use Entry.getKey, Entry.getValue
Expand Down Expand Up @@ -254,27 +270,27 @@ HttpHandler handler() {
}
}

private static class PluginAdminEndpointRoute extends Route {
private final NamedPlugin namedPlugin;
private static class AdminEndpointRoute extends Route {
private final String root;
private final String name;

PluginAdminEndpointRoute(NamedPlugin namedPlugin, String relativePath, HttpHandler handler) {
super(pluginAdminEndpointPath(namedPlugin, relativePath), handler);

this.namedPlugin = namedPlugin;
AdminEndpointRoute(String root, String name, String relativePath, HttpHandler handler) {
super(adminEndpointPath(root, name, relativePath), handler);
this.root = root;
this.name = name;
}

static String pluginAdminEndpointPath(NamedPlugin namedPlugin, String relativePath) {
return pluginPath(namedPlugin) + "/" + dropFirstForwardSlash(relativePath);
static String adminEndpointPath(String root, String name, String relativePath) {
return adminPath(root, name) + "/" + dropFirstForwardSlash(relativePath);
}

static String dropFirstForwardSlash(String key) {
return key.charAt(0) == '/' ? key.substring(1) : key;
}

String linkLabel() {
String relativePath = path().substring(pluginPath(namedPlugin).length() + 1);

return namedPlugin.name() + ": " + dropFirstForwardSlash(relativePath);
String relativePath = path().substring(adminPath(root, name).length() + 1);
return name + ": " + dropFirstForwardSlash(relativePath);
}

IndexHandler.Link link() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (C) 2013-2018 Expedia Inc.
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.
Expand All @@ -20,13 +20,11 @@

import static com.hotels.styx.routing.config.ConfigVersionResolver.Version.ROUTING_CONFIG_V1;
import static com.hotels.styx.routing.config.ConfigVersionResolver.Version.ROUTING_CONFIG_V2;
import static java.util.Objects.requireNonNull;

/**
* Works out the Styx configuration version for backwards compatibility purposes.
*/
public class ConfigVersionResolver {
private final StyxConfig config;
public final class ConfigVersionResolver {

/**
* Routing configuration version.
Expand All @@ -36,11 +34,10 @@ public enum Version {
ROUTING_CONFIG_V2
};

public ConfigVersionResolver(StyxConfig config) {
this.config = requireNonNull(config);
private ConfigVersionResolver() {
}

public Version version() {
public static Version configVersion(StyxConfig config) {
if (config.get("httpPipeline", JsonNode.class).isPresent()) {
return ROUTING_CONFIG_V2;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ private StyxServerComponents(Builder builder) {

// TODO In further refactoring, we will probably want this loading to happen outside of this constructor call,
// so that it doesn't delay the admin server from starting up
this.plugins = builder.configuredPluginFactories == null
this.plugins = builder.configuredPluginFactories.isEmpty()
? loadPlugins(environment)
: loadPlugins(environment, builder.configuredPluginFactories);

Expand Down Expand Up @@ -241,7 +241,7 @@ private static Map<String, StyxService> mergeServices(Map<String, StyxService> c
public static final class Builder {
private StyxConfig styxConfig;
private LoggingSetUp loggingSetUp = DO_NOT_MODIFY;
private List<ConfiguredPluginFactory> configuredPluginFactories;
private List<ConfiguredPluginFactory> configuredPluginFactories = ImmutableList.of();
private ServicesLoader servicesLoader = SERVICES_FROM_CONFIG;
private MetricRegistry metricRegistry = new CodaHaleMetricRegistry();
private StartupConfig startupConfig;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package com.hotels.styx.routing.config;

import com.hotels.styx.StyxConfig
import com.hotels.styx.routing.config.ConfigVersionResolver.Version.ROUTING_CONFIG_V2
import com.hotels.styx.routing.config.ConfigVersionResolver.configVersion
import io.kotlintest.shouldBe
import io.kotlintest.specs.StringSpec

Expand All @@ -35,7 +36,7 @@ class ConfigVersionResolverTest : StringSpec({
bar: 1
""".trimIndent(), false)

ConfigVersionResolver(config).version().shouldBe(ROUTING_CONFIG_V2)
configVersion(config).shouldBe(ROUTING_CONFIG_V2)
}

})
Loading