diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/DeepRuntimeException.java b/agent-api/src/main/java/com/intergral/deep/agent/api/DeepRuntimeException.java new file mode 100644 index 0000000..d8b7deb --- /dev/null +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/DeepRuntimeException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.api; + +public class DeepRuntimeException extends RuntimeException { + + public DeepRuntimeException(final String message) { + super(message); + } + + public DeepRuntimeException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/IDeep.java b/agent-api/src/main/java/com/intergral/deep/agent/api/IDeep.java index 389ad71..2148787 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/IDeep.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/IDeep.java @@ -30,6 +30,25 @@ */ public interface IDeep { + /** + * Get the current state of deep. + * + * @return {@code true} if deep is currently enabled and sending requests, else {@code false} + */ + boolean isEnabled(); + + + /** + * This method can be used to disabled or enable Deep. + *

+ * Changing the state to {@code false} (ie disabled) will cause deep to uninstall all the tracepoints and clear the current config. + * Meaning that when deep is enabled again it will have to reinstall the configuration. It is therefore advised to not call this function + * too frequently. + * + * @param enabled the new state to become + */ + void setEnabled(final boolean enabled); + /** * Get the version of deep being used. * diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/IRegistration.java b/agent-api/src/main/java/com/intergral/deep/agent/api/IRegistration.java index 973d1da..df32f9c 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/IRegistration.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/IRegistration.java @@ -20,10 +20,17 @@ /** * This is a generic interface from the result of a registration. */ -public interface IRegistration { +public interface IRegistration { /** * Unregister the item registered. */ void unregister(); + + /** + * Get the registered item + * + * @return the item that this registration is for. + */ + T get(); } diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/auth/AuthProvider.java b/agent-api/src/main/java/com/intergral/deep/agent/api/auth/AuthProvider.java index 8d59b82..4a4227d 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/auth/AuthProvider.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/auth/AuthProvider.java @@ -17,6 +17,8 @@ package com.intergral.deep.agent.api.auth; +import com.intergral.deep.agent.api.DeepRuntimeException; +import com.intergral.deep.agent.api.plugin.IPlugin; import com.intergral.deep.agent.api.settings.ISettings; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -32,6 +34,18 @@ public static IAuthProvider provider(final ISettings settings) { if (serviceAuthProvider == null || serviceAuthProvider.trim().isEmpty()) { return NOOP_PROVIDER; } + + // check if we have a plugin of this name that we can use + final IPlugin plugin = settings.getPlugin(serviceAuthProvider); + if (plugin != null) { + if (plugin instanceof IAuthProvider) { + return (IAuthProvider) plugin; + } else { + throw new DeepRuntimeException( + String.format("Cannot use plugin %s as auth provider, must implement IAuthProvider interface.", plugin.name())); + } + } + try { final Class aClass = Class.forName(serviceAuthProvider); final Constructor constructor = aClass.getConstructor(ISettings.class); @@ -42,7 +56,7 @@ public static IAuthProvider provider(final ISettings settings) { | InvocationTargetException | InstantiationException | IllegalAccessException e) { - throw new RuntimeException(e); + throw new RuntimeException(String.format("Cannot load auth provider %s", serviceAuthProvider), e); } } diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IPlugin.java b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IPlugin.java index b0fd9a7..1a05365 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IPlugin.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IPlugin.java @@ -70,7 +70,14 @@ default boolean isActive(final ISettings settings) { /** * This type describes a registered plugin. */ - interface IPluginRegistration extends IRegistration { + interface IPluginRegistration extends IRegistration { + /** + * Indicates if this plugin is currently set to be the auth provider + * + * @return {@code true} if the registered plugin is an {@link com.intergral.deep.agent.api.auth.IAuthProvider} and deep is configured to + * use this provider, else {@code false} + */ + boolean isAuthProvider(); } } diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ConfigurationException.java b/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ConfigurationException.java index c4a2edd..c560a81 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ConfigurationException.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ConfigurationException.java @@ -12,6 +12,8 @@ public final class ConfigurationException extends RuntimeException { /** * Create a new configuration exception with specified {@code message} and without a cause. + * + * @param message The exception message */ public ConfigurationException(String message) { super(message); @@ -19,6 +21,9 @@ public ConfigurationException(String message) { /** * Create a new configuration exception with specified {@code message} and {@code cause}. + * + * @param message The exception message + * @param cause The root cause of this exception */ public ConfigurationException(String message, Throwable cause) { super(message, cause); diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/settings/ISettings.java b/agent-api/src/main/java/com/intergral/deep/agent/api/settings/ISettings.java index c071d03..8fa00c0 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/settings/ISettings.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/settings/ISettings.java @@ -17,14 +17,43 @@ package com.intergral.deep.agent.api.settings; +import com.intergral.deep.agent.api.plugin.IPlugin; import com.intergral.deep.agent.api.resource.Resource; import java.util.Map; public interface ISettings { + /** + * This is the settings key for the configured auth provider + */ + String KEY_AUTH_PROVIDER = "service.auth.provider"; + + /** + * This is the setting key for enabling or disabling deep. + */ + String KEY_ENABLED = "enabled"; + + /** + * This is the setting key for the service url + */ + String KEY_SERVICE_URL = "service.url"; + T getSettingAs(String key, Class clazz); Map getMap(String attributeProperty); + /** + * Returns the resource that describes this client + * + * @return the {@link Resource} + */ Resource getResource(); + + /** + * Look for a plugin with the given name or class name. + * + * @param name the plugin name or the plugin class name + * @return the {@link IPlugin} or {@code null} + */ + IPlugin getPlugin(final String name); } diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/spi/Ordered.java b/agent-api/src/main/java/com/intergral/deep/agent/api/spi/Ordered.java index afb1d4a..c1b8f74 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/spi/Ordered.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/spi/Ordered.java @@ -16,6 +16,8 @@ public interface Ordered { * Returns the order of applying the SPI implementing this interface. Higher values are applied * later, for example: an SPI with order=1 will run after an SPI with order=0. SPI implementations * with equal values will be run in a non-deterministic order. + * + * @return the order value */ default int order() { return 0; diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/tracepoint/ITracepoint.java b/agent-api/src/main/java/com/intergral/deep/agent/api/tracepoint/ITracepoint.java index de79c61..4216275 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/tracepoint/ITracepoint.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/tracepoint/ITracepoint.java @@ -41,7 +41,7 @@ default String id() { return UUID.randomUUID().toString(); } - interface ITracepointRegistration extends IRegistration { + interface ITracepointRegistration extends IRegistration { } } diff --git a/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java b/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java index 273e0a9..48126b1 100644 --- a/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java +++ b/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java @@ -19,9 +19,12 @@ import com.intergral.deep.agent.api.DeepVersion; import com.intergral.deep.agent.api.IDeep; +import com.intergral.deep.agent.api.auth.IAuthProvider; import com.intergral.deep.agent.api.plugin.IPlugin; import com.intergral.deep.agent.api.plugin.IPlugin.IPluginRegistration; import com.intergral.deep.agent.api.resource.Resource; +import com.intergral.deep.agent.api.settings.ISettings; +import com.intergral.deep.agent.api.tracepoint.ITracepoint; import com.intergral.deep.agent.api.tracepoint.ITracepoint.ITracepointRegistration; import com.intergral.deep.agent.grpc.GrpcService; import com.intergral.deep.agent.plugins.PluginLoader; @@ -74,7 +77,29 @@ public String getVersion() { public IPluginRegistration registerPlugin(final IPlugin plugin) { this.settings.addPlugin(plugin); - return () -> this.settings.removePlugin(plugin); + final boolean isAuthProvider; + if (plugin instanceof IAuthProvider) { + final String settingAs = this.settings.getSettingAs(ISettings.KEY_AUTH_PROVIDER, String.class); + isAuthProvider = settingAs != null && settingAs.equals(plugin.getClass().getName()); + } else { + isAuthProvider = false; + } + return new IPluginRegistration() { + @Override + public boolean isAuthProvider() { + return isAuthProvider; + } + + @Override + public void unregister() { + settings.removePlugin(plugin); + } + + @Override + public IPlugin get() { + return plugin; + } + }; } @Override @@ -86,6 +111,62 @@ public ITracepointRegistration registerTracepoint(final String path, final int l public ITracepointRegistration registerTracepoint(final String path, final int line, final Map args, final Collection watches) { final TracePointConfig tracePointConfig = this.tracepointConfig.addCustom(path, line, args, watches); - return () -> this.tracepointConfig.removeCustom(tracePointConfig); + return new ITracepointRegistration() { + @Override + public void unregister() { + tracepointConfig.removeCustom(tracePointConfig); + } + + @Override + public ITracepoint get() { + return new ITracepoint() { + @Override + public String path() { + return path; + } + + @Override + public int line() { + return line; + } + + @Override + public Map args() { + return args; + } + + @Override + public Collection watches() { + return watches; + } + + @Override + public String id() { + return tracePointConfig.getId(); + } + }; + } + }; + } + + @Override + public boolean isEnabled() { + return this.settings.getSettingAs(ISettings.KEY_ENABLED, Boolean.class); + } + + @Override + public synchronized void setEnabled(final boolean enabled) { + // we are already the desired state - so do nothing + if (isEnabled() == enabled) { + return; + } + + // update config to new state + this.settings.setActive(enabled); + + // if we are disabling then we need to clear configs + if (!enabled) { + this.tracepointConfig.configUpdate(0, null, Collections.emptyList()); + } } } diff --git a/agent/src/main/java/com/intergral/deep/agent/poll/LongPollService.java b/agent/src/main/java/com/intergral/deep/agent/poll/LongPollService.java index 560c7d7..bb7ed72 100644 --- a/agent/src/main/java/com/intergral/deep/agent/poll/LongPollService.java +++ b/agent/src/main/java/com/intergral/deep/agent/poll/LongPollService.java @@ -56,6 +56,11 @@ public void start(final ITracepointConfig tracepointConfig) { @Override public void run(long now) { + if (!this.settings.isActive()) { + // we have been disabled so skip this poll + // we will pause like normal and try again later + return; + } final PollConfigGrpc.PollConfigBlockingStub blockingStub = this.grpcService.pollService(); final PollRequest.Builder builder = PollRequest.newBuilder(); @@ -69,7 +74,10 @@ public void run(long now) { .build(); final PollResponse response = blockingStub.poll(pollRequest); - + // check we are still active + if (!this.settings.isActive()) { + return; + } if (response.getResponseType() == ResponseType.NO_CHANGE) { this.tracepointConfig.noChange(response.getTsNanos()); } else { diff --git a/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java b/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java index ca35824..90e7866 100644 --- a/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java +++ b/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java @@ -35,11 +35,13 @@ import java.util.Map; import java.util.Optional; import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.regex.Pattern; public class Settings implements ISettings { + private static final AtomicBoolean IS_ACTIVE = new AtomicBoolean(true); private final Properties properties; private Resource resource; private Collection plugins; @@ -62,6 +64,12 @@ public static Settings build(final Map agentArgs) { propertiesStream = Settings.class.getResourceAsStream("/deep_settings.properties"); } + // we have special handling for is active as it is the only value that we allow to change during run time. + final String isActive = readProperty(ISettings.KEY_ENABLED, agentArgs); + if (isActive != null && !Boolean.parseBoolean(isActive)) { + IS_ACTIVE.set(false); + } + return build(agentArgs, propertiesStream); } @@ -178,6 +186,10 @@ private static List makeList(final String str) { } public T getSettingAs(String key, Class clazz) { + // special handling for enabled key + if(key.equals(ISettings.KEY_ENABLED)){ + return coerc(String.valueOf(isActive()), clazz); + } final String property = this.properties.getProperty(key); if (property != null) { @@ -206,33 +218,33 @@ public Map getMap(String key) { } public String getServiceHost() { - final String serviceUrl = getSettingAs("service.url", String.class); + final String serviceUrl = getSettingAs(ISettings.KEY_SERVICE_URL, String.class); if (serviceUrl.contains("://")) { try { return new URL(serviceUrl).getHost(); } catch (MalformedURLException e) { - throw new InvalidConfigException("service.url", serviceUrl, e); + throw new InvalidConfigException(ISettings.KEY_SERVICE_URL, serviceUrl, e); } } else if (serviceUrl.contains(":")) { return serviceUrl.split(":")[0]; } - throw new InvalidConfigException("service.url", serviceUrl); + throw new InvalidConfigException(ISettings.KEY_SERVICE_URL, serviceUrl); } public int getServicePort() { - final String serviceUrl = getSettingAs("service.url", String.class); + final String serviceUrl = getSettingAs(ISettings.KEY_SERVICE_URL, String.class); if (serviceUrl.contains("://")) { try { return new URL(serviceUrl).getPort(); } catch (MalformedURLException e) { - throw new InvalidConfigException("service.url", serviceUrl, e); + throw new InvalidConfigException(ISettings.KEY_SERVICE_URL, serviceUrl, e); } } else if (serviceUrl.contains(":")) { return Integer.parseInt(serviceUrl.split(":")[1]); } - throw new InvalidConfigException("service.url", serviceUrl); + throw new InvalidConfigException(ISettings.KEY_SERVICE_URL, serviceUrl); } @Override @@ -274,6 +286,25 @@ public void removePlugin(final IPlugin plugin) { this.plugins.removeIf(iPlugin -> iPlugin.name().equals(plugin.name())); } + @Override + public IPlugin getPlugin(final String name) { + final Collection allPlugins = this.getPlugins(); + for (IPlugin plugin : allPlugins) { + if (plugin.name().equals(name) || plugin.getClass().getName().equals(name)) { + return plugin; + } + } + return null; + } + + public boolean isActive() { + return IS_ACTIVE.get(); + } + + public void setActive(boolean state) { + IS_ACTIVE.set(state); + } + public static class InvalidConfigException extends RuntimeException { public InvalidConfigException(String key, String value) { diff --git a/examples/dynamic-load/src/main/java/com/intergral/deep/examples/Main.java b/examples/dynamic-load/src/main/java/com/intergral/deep/examples/Main.java index e4d5e85..a336fec 100644 --- a/examples/dynamic-load/src/main/java/com/intergral/deep/examples/Main.java +++ b/examples/dynamic-load/src/main/java/com/intergral/deep/examples/Main.java @@ -23,6 +23,7 @@ import com.intergral.deep.agent.api.IDeep; import com.intergral.deep.agent.api.reflection.IReflection; import com.intergral.deep.agent.api.resource.Resource; +import com.intergral.deep.agent.api.settings.ISettings; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; @@ -51,7 +52,7 @@ public static void main(String[] args) throws Throwable { // Dynamically configure and attach the deep agent Deep.config() .setJarPath(jarPath.toAbsolutePath().toString()) - .setValue("service.url", "localhost:43315") + .setValue(ISettings.KEY_SERVICE_URL, "localhost:43315") .setValue("service.secure", false) .start();