http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/com/twitter/common/zookeeper/testing/ZooKeeperTestServer.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/com/twitter/common/zookeeper/testing/ZooKeeperTestServer.java b/commons/src/main/java/com/twitter/common/zookeeper/testing/ZooKeeperTestServer.java deleted file mode 100644 index a199d1d..0000000 --- a/commons/src/main/java/com/twitter/common/zookeeper/testing/ZooKeeperTestServer.java +++ /dev/null @@ -1,222 +0,0 @@ -/** - * 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.twitter.common.zookeeper.testing; - -import java.io.File; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.util.Arrays; - -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; - -import org.apache.zookeeper.server.NIOServerCnxn; -import org.apache.zookeeper.server.ZooKeeperServer; -import org.apache.zookeeper.server.ZooKeeperServer.BasicDataTreeBuilder; -import org.apache.zookeeper.server.persistence.FileTxnSnapLog; - -import com.twitter.common.application.ShutdownRegistry; -import com.twitter.common.base.Command; -import com.twitter.common.base.ExceptionalCommand; -import com.twitter.common.io.FileUtils; -import com.twitter.common.quantity.Amount; -import com.twitter.common.quantity.Time; -import com.twitter.common.zookeeper.ZooKeeperClient; -import com.twitter.common.zookeeper.ZooKeeperClient.Credentials; - -/** - * A helper class for starting in-process ZooKeeper server and clients. - * - * <p>This is ONLY meant to be used for testing. - */ -public class ZooKeeperTestServer { - - /** - * The default session timeout for clients created by servers constructed with - * {@link #ZooKeeperTestServer(int, ShutdownRegistry)}. - */ - public static final Amount<Integer, Time> DEFAULT_SESSION_TIMEOUT = - Amount.of(100, Time.MILLISECONDS); - - protected final ZooKeeperServer zooKeeperServer; - private final ShutdownRegistry shutdownRegistry; - private NIOServerCnxn.Factory connectionFactory; - private int port; - private final Amount<Integer, Time> defaultSessionTimeout; - - /** - * @param port the port to start the zoo keeper server on - {@code 0} picks an ephemeral port - * @param shutdownRegistry a registry that will be used to register client and server shutdown - * commands. It is up to the caller to execute the registered actions at an appropriate time. - * @throws IOException if there was aproblem creating the server's database - */ - public ZooKeeperTestServer(int port, ShutdownRegistry shutdownRegistry) throws IOException { - this(port, shutdownRegistry, DEFAULT_SESSION_TIMEOUT); - } - - /** - * @param port the port to start the zoo keeper server on - {@code 0} picks an ephemeral port - * @param shutdownRegistry a registry that will be used to register client and server shutdown - * commands. It is up to the caller to execute the registered actions at an appropriate time. - * @param defaultSessionTimeout the default session timeout for clients created with - * {@link #createClient()}. - * @throws IOException if there was aproblem creating the server's database - */ - public ZooKeeperTestServer(int port, ShutdownRegistry shutdownRegistry, - Amount<Integer, Time> defaultSessionTimeout) throws IOException { - Preconditions.checkArgument(0 <= port && port <= 0xFFFF); - this.port = port; - this.shutdownRegistry = Preconditions.checkNotNull(shutdownRegistry); - this.defaultSessionTimeout = Preconditions.checkNotNull(defaultSessionTimeout); - - zooKeeperServer = - new ZooKeeperServer( - new FileTxnSnapLog(createTempDir(), createTempDir()), - new BasicDataTreeBuilder()) { - - // TODO(John Sirois): Introduce a builder to configure the in-process server if and when - // some folks need JMX for in-process tests. - @Override protected void registerJMX() { - // noop - } - }; - } - - /** - * Starts zookeeper up on the configured port. If the configured port is the ephemeral port - * (@{code 0}), then the actual chosen port is returned. - */ - public final int startNetwork() throws IOException, InterruptedException { - connectionFactory = new NIOServerCnxn.Factory(new InetSocketAddress(port)); - connectionFactory.startup(zooKeeperServer); - shutdownRegistry.addAction(new Command() { - @Override public void execute() { - shutdownNetwork(); - } - }); - port = zooKeeperServer.getClientPort(); - return port; - } - - /** - * Starts zookeeper back up on the last used port. - */ - public final void restartNetwork() throws IOException, InterruptedException { - checkEphemeralPortAssigned(); - Preconditions.checkState(!connectionFactory.isAlive()); - startNetwork(); - } - - /** - * Shuts down the in-process zookeeper network server. - */ - public final void shutdownNetwork() { - if (connectionFactory != null && connectionFactory.isAlive()) { - connectionFactory.shutdown(); - } - } - - /** - * Expires the active session for the given client. The client should be one returned from - * {@link #createClient}. - * - * @param zkClient the client to expire - * @throws ZooKeeperClient.ZooKeeperConnectionException if a problem is encountered connecting to - * the local zk server while trying to expire the session - * @throws InterruptedException if interrupted while requesting expiration - */ - public final void expireClientSession(ZooKeeperClient zkClient) - throws ZooKeeperClient.ZooKeeperConnectionException, InterruptedException { - zooKeeperServer.closeSession(zkClient.get().getSessionId()); - } - - /** - * Returns the current port to connect to the in-process zookeeper instance. - */ - public final int getPort() { - checkEphemeralPortAssigned(); - return port; - } - - /** - * Returns a new unauthenticated zookeeper client connected to the in-process zookeeper server - * with the default session timeout. - */ - public final ZooKeeperClient createClient() { - return createClient(defaultSessionTimeout); - } - - /** - * Returns a new unauthenticated zookeeper client connected to the in-process zookeeper server - * with the default session timeout and a custom {@code chrootPath}. - */ - public final ZooKeeperClient createClient(String chrootPath) { - return createClient(defaultSessionTimeout, Credentials.NONE, Optional.of(chrootPath)); - } - - /** - * Returns a new authenticated zookeeper client connected to the in-process zookeeper server with - * the default session timeout. - */ - public final ZooKeeperClient createClient(Credentials credentials) { - return createClient(defaultSessionTimeout, credentials, Optional.<String>absent()); - } - - /** - * Returns a new unauthenticated zookeeper client connected to the in-process zookeeper server - * with a custom {@code sessionTimeout}. - */ - public final ZooKeeperClient createClient(Amount<Integer, Time> sessionTimeout) { - return createClient(sessionTimeout, Credentials.NONE, Optional.<String>absent()); - } - - /** - * Returns a new authenticated zookeeper client connected to the in-process zookeeper server with - * a custom {@code sessionTimeout}. - */ - public final ZooKeeperClient createClient(Amount<Integer, Time> sessionTimeout, - Credentials credentials) { - return createClient(sessionTimeout, credentials, Optional.<String>absent()); - } - - /** - * Returns a new authenticated zookeeper client connected to the in-process zookeeper server with - * a custom {@code sessionTimeout} and a custom {@code chrootPath}. - */ - public final ZooKeeperClient createClient(Amount<Integer, Time> sessionTimeout, - Credentials credentials, Optional<String> chrootPath) { - final ZooKeeperClient client = new ZooKeeperClient(sessionTimeout, credentials, - chrootPath, Arrays.asList(InetSocketAddress.createUnresolved("127.0.0.1", port))); - shutdownRegistry.addAction(new ExceptionalCommand<InterruptedException>() { - @Override public void execute() { - client.close(); - } - }); - return client; - } - - private void checkEphemeralPortAssigned() { - Preconditions.checkState(port > 0, "startNetwork must be called first"); - } - - private File createTempDir() { - final File tempDir = FileUtils.createTempDir(); - shutdownRegistry.addAction(new ExceptionalCommand<IOException>() { - @Override public void execute() throws IOException { - org.apache.commons.io.FileUtils.deleteDirectory(tempDir); - } - }); - return tempDir; - } -}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/AbstractApplication.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/AbstractApplication.java b/commons/src/main/java/org/apache/aurora/common/application/AbstractApplication.java new file mode 100644 index 0000000..afb3691 --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/AbstractApplication.java @@ -0,0 +1,29 @@ +/** + * 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 org.apache.aurora.common.application; + +import java.util.Collections; + +import com.google.inject.Module; + +/** + * A base application class that provides empty implementations of all but the {@link #run()} + * method. + */ +public abstract class AbstractApplication implements Application { + @Override + public Iterable<? extends Module> getModules() { + return Collections.emptyList(); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/AppLauncher.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/AppLauncher.java b/commons/src/main/java/org/apache/aurora/common/application/AppLauncher.java new file mode 100644 index 0000000..c842f42 --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/AppLauncher.java @@ -0,0 +1,202 @@ +/** + * 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 org.apache.aurora.common.application; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Module; +import com.google.inject.Stage; +import com.google.inject.util.Modules; + +import org.apache.aurora.common.application.modules.AppLauncherModule; +import org.apache.aurora.common.application.modules.LifecycleModule; +import org.apache.aurora.common.args.Arg; +import org.apache.aurora.common.args.ArgFilters; +import org.apache.aurora.common.args.ArgScanner; +import org.apache.aurora.common.args.ArgScanner.ArgScanException; +import org.apache.aurora.common.args.CmdLine; +import org.apache.aurora.common.args.constraints.NotNull; +import org.apache.aurora.common.base.ExceptionalCommand; + +/** + * An application launcher that sets up a framework for pluggable binding modules. This class + * should be called directly as the main class, with a command line argument {@code -app_class} + * which is the canonical class name of the application to execute. + * + * If your application uses command line arguments all {@link Arg} fields annotated with + * {@link CmdLine} will be discovered and command line arguments will be validated against this set, + * parsed and applied. + * + * A bootstrap module will be automatically applied ({@link AppLauncherModule}), which provides + * overridable default bindings for things like quit/abort hooks and a health check function. + * A {@link LifecycleModule} is also automatically applied to perform startup and shutdown + * actions. + */ +public final class AppLauncher { + + private static final Logger LOG = Logger.getLogger(AppLauncher.class.getName()); + + private static final String APP_CLASS_NAME = "app_class"; + @NotNull + @CmdLine(name = APP_CLASS_NAME, + help = "Fully-qualified name of the application class, which must implement Runnable.") + private static final Arg<Class<? extends Application>> APP_CLASS = Arg.create(); + + @CmdLine(name = "guice_stage", + help = "Guice development stage to create injector with.") + private static final Arg<Stage> GUICE_STAGE = Arg.create(Stage.DEVELOPMENT); + + private static final Predicate<Field> SELECT_APP_CLASS = + ArgFilters.selectCmdLineArg(AppLauncher.class, APP_CLASS_NAME); + + @Inject @StartupStage private ExceptionalCommand startupCommand; + @Inject private Lifecycle lifecycle; + + private AppLauncher() { + // This should not be invoked directly. + } + + private void run(Application application) { + try { + configureInjection(application); + + LOG.info("Executing startup actions."); + // We're an app framework and this is the outer shell - it makes sense to handle all errors + // before exiting. + // SUPPRESS CHECKSTYLE:OFF IllegalCatch + try { + startupCommand.execute(); + } catch (Exception e) { + LOG.log(Level.SEVERE, "Startup action failed, quitting.", e); + throw Throwables.propagate(e); + } + // SUPPRESS CHECKSTYLE:ON IllegalCatch + + try { + application.run(); + } finally { + LOG.info("Application run() exited."); + } + } finally { + if (lifecycle != null) { + lifecycle.shutdown(); + } + } + } + + private void configureInjection(Application application) { + Iterable<Module> modules = ImmutableList.<Module>builder() + .add(new LifecycleModule()) + .add(new AppLauncherModule()) + .addAll(application.getModules()) + .build(); + + Injector injector = Guice.createInjector(GUICE_STAGE.get(), Modules.combine(modules)); + injector.injectMembers(this); + injector.injectMembers(application); + } + + public static void main(String... args) throws IllegalAccessException, InstantiationException { + // TODO(John Sirois): Support a META-INF/MANIFEST.MF App-Class attribute to allow java -jar + parseArgs(ArgFilters.SELECT_ALL, Arrays.asList(args)); + new AppLauncher().run(APP_CLASS.get().newInstance()); + } + + /** + * A convenience for main wrappers. Equivalent to: + * <pre> + * AppLauncher.launch(appClass, ArgFilters.SELECT_ALL, Arrays.asList(args)); + * </pre> + * + * @param appClass The application class to instantiate and launch. + * @param args The command line arguments to parse. + * @see ArgFilters + */ + public static void launch(Class<? extends Application> appClass, String... args) { + launch(appClass, ArgFilters.SELECT_ALL, Arrays.asList(args)); + } + + /** + * A convenience for main wrappers. Equivalent to: + * <pre> + * AppLauncher.launch(appClass, argFilter, Arrays.asList(args)); + * </pre> + * + * @param appClass The application class to instantiate and launch. + * @param argFilter A filter that selects the {@literal @CmdLine} {@link Arg}s to enable for + * parsing. + * @param args The command line arguments to parse. + * @see ArgFilters + */ + public static void launch(Class<? extends Application> appClass, Predicate<Field> argFilter, + String... args) { + launch(appClass, argFilter, Arrays.asList(args)); + } + + /** + * Used to launch an application with a restricted set of {@literal @CmdLine} {@link Arg}s + * considered for parsing. This is useful if the classpath includes annotated fields you do not + * wish arguments to be parsed for. + * + * @param appClass The application class to instantiate and launch. + * @param argFilter A filter that selects the {@literal @CmdLine} {@link Arg}s to enable for + * parsing. + * @param args The command line arguments to parse. + * @see ArgFilters + */ + public static void launch(Class<? extends Application> appClass, Predicate<Field> argFilter, + List<String> args) { + Preconditions.checkNotNull(appClass); + Preconditions.checkNotNull(argFilter); + Preconditions.checkNotNull(args); + + parseArgs(Predicates.<Field>and(Predicates.not(SELECT_APP_CLASS), argFilter), args); + try { + new AppLauncher().run(appClass.newInstance()); + } catch (InstantiationException e) { + throw new IllegalStateException(e); + } catch (IllegalAccessException e) { + throw new IllegalStateException(e); + } + } + + private static void parseArgs(Predicate<Field> filter, List<String> args) { + try { + if (!new ArgScanner().parse(filter, args)) { + System.exit(0); + } + } catch (ArgScanException e) { + exit("Failed to scan arguments", e); + } catch (IllegalArgumentException e) { + exit("Failed to apply arguments", e); + } + } + + private static void exit(String message, Exception error) { + LOG.log(Level.SEVERE, message + "\n" + error, error); + System.exit(1); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/Application.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/Application.java b/commons/src/main/java/org/apache/aurora/common/application/Application.java new file mode 100644 index 0000000..ebe01e8 --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/Application.java @@ -0,0 +1,29 @@ +/** + * 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 org.apache.aurora.common.application; + +import com.google.inject.Module; + +/** + * An application that supports a limited lifecycle and optional binding of guice modules. + */ +public interface Application extends Runnable { + + /** + * Returns binding modules for the application. + * + * @return Application binding modules. + */ + Iterable<? extends Module> getModules(); +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/Lifecycle.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/Lifecycle.java b/commons/src/main/java/org/apache/aurora/common/application/Lifecycle.java new file mode 100644 index 0000000..5d22a2a --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/Lifecycle.java @@ -0,0 +1,94 @@ +/** + * 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 org.apache.aurora.common.application; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.logging.Logger; + +import com.google.inject.Inject; + +import org.apache.aurora.common.base.Command; + +/** + * Application lifecycle manager, which coordinates orderly shutdown of an application. This class + * is responsible for executing shutdown commands, and can also be used to allow threads to await + * application shutdown. + * + * @author William Farner + */ +public class Lifecycle { + + private static final Logger LOG = Logger.getLogger(Lifecycle.class.getName()); + + // Monitor and state for suspending and terminating execution. + private final Object waitMonitor = new Object(); + private boolean destroyed = false; + + private final Command shutdownRegistry; + + @Inject + public Lifecycle(@ShutdownStage Command shutdownRegistry, + UncaughtExceptionHandler exceptionHandler) { + + this.shutdownRegistry = shutdownRegistry; + Thread.setDefaultUncaughtExceptionHandler(exceptionHandler); + } + + /** + * Checks whether this lifecycle is still considered alive. The lifecycle is still alive until + * {@link #shutdown()} has been called and all of the actions registered with the shutdown + * controller have completed. + * + * @return {@code true} if the lifecycle is alive, {@code false} otherwise. + * + */ + public final boolean isAlive() { + synchronized (waitMonitor) { + return !destroyed; + } + } + + /** + * Allows a caller to wait forever; typically used when all work is done in daemon threads. + * Will exit on interrupts. + */ + public final void awaitShutdown() { + LOG.info("Awaiting shutdown"); + synchronized (waitMonitor) { + while (!destroyed) { + try { + waitMonitor.wait(); + } catch (InterruptedException e) { + LOG.info("Exiting on interrupt"); + shutdown(); + return; + } + } + } + } + + /** + * Initiates an orderly shutdown of the lifecycle's registered shutdown hooks. + */ + public final void shutdown() { + synchronized (waitMonitor) { + if (!destroyed) { + destroyed = true; + LOG.info("Shutting down application"); + shutdownRegistry.execute(); + waitMonitor.notifyAll(); + } + } + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/ShutdownRegistry.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/ShutdownRegistry.java b/commons/src/main/java/org/apache/aurora/common/application/ShutdownRegistry.java new file mode 100644 index 0000000..b440e7e --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/ShutdownRegistry.java @@ -0,0 +1,99 @@ +/** + * 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 org.apache.aurora.common.application; + +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; + +import org.apache.aurora.common.base.Command; +import org.apache.aurora.common.base.ExceptionalCommand; + +/** + * A shutdown action controller. It executes actions in the reverse order they were registered, and + * logs a warning for every shutdown action that fails, but doesn't prevent completion of subsequent + * actions or the normal completion of the {@code execute()} method. + * + * @author Attila Szegedi + */ +public interface ShutdownRegistry { + + /** + * Adds an action to the shutdown registry. + * + * @param action Action to register. + * @param <E> Exception type thrown by the action. + * @param <T> Type of command. + */ + <E extends Exception, T extends ExceptionalCommand<E>> void addAction(T action); + + /** + * Implementation of a shutdown registry. + */ + public static class ShutdownRegistryImpl implements ShutdownRegistry, Command { + private static final Logger LOG = Logger.getLogger(ShutdownRegistry.class.getName()); + + private final List<ExceptionalCommand<? extends Exception>> actions = Lists.newLinkedList(); + + private boolean completed = false; + + /** + * Registers an action to execute during {@link #execute()}. It is an error to call this method + * after calling {@link #execute()}. + * + * @param action the action to add to the list of actions to execute during execution + */ + @Override + public synchronized <E extends Exception, T extends ExceptionalCommand<E>> void addAction( + T action) { + Preconditions.checkState(!completed); + actions.add(action); + } + + /** + * Executes an application shutdown stage by executing all registered actions. This method can + * be called multiple times but will only execute the registered actions the first time. + * + * This sends output to System.out because logging is unreliable during JVM shutdown, which + * this class may be used for. + */ + @Override + public synchronized void execute() { + if (!completed) { + LOG.info(String.format("Executing %d shutdown commands.", actions.size())); + completed = true; + try { + for (ExceptionalCommand<? extends Exception> action : Lists.reverse(actions)) { + // Part of our contract is ensuring each shutdown action executes so we must catch all + // exceptions. + // SUPPRESS CHECKSTYLE:OFF IllegalCatch + try { + action.execute(); + } catch (Exception e) { + LOG.log(Level.WARNING, "Shutdown action failed.", e); + } + // SUPPRESS CHECKSTYLE:ON IllegalCatch + } + } finally { + actions.clear(); + } + } else { + LOG.info("Action controller has already completed, subsequent calls ignored."); + } + } + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/ShutdownStage.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/ShutdownStage.java b/commons/src/main/java/org/apache/aurora/common/application/ShutdownStage.java new file mode 100644 index 0000000..95f9c58 --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/ShutdownStage.java @@ -0,0 +1,31 @@ +/** + * 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 org.apache.aurora.common.application; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import com.google.inject.BindingAnnotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Binding annotation used for the shutdown registry. + */ +@BindingAnnotation +@Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME) +public @interface ShutdownStage { } http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/StartupRegistry.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/StartupRegistry.java b/commons/src/main/java/org/apache/aurora/common/application/StartupRegistry.java new file mode 100644 index 0000000..997ee77 --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/StartupRegistry.java @@ -0,0 +1,52 @@ +/** + * 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 org.apache.aurora.common.application; + +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Logger; + +import com.google.common.base.Preconditions; +import com.google.inject.Inject; + +import org.apache.aurora.common.base.ExceptionalCommand; + +/** + * A registry that executes a set of commands. The registry will synchronously execute commands + * when {@link #execute()} is invoked, returning early if any action throws an exception. + * Only one call to {@link #execute()} will have an effect, all subsequent calls will be ignored. + */ +public class StartupRegistry implements ExceptionalCommand<Exception> { + + private static final Logger LOG = Logger.getLogger(StartupRegistry.class.getName()); + + private final Set<ExceptionalCommand> startupActions; + private final AtomicBoolean started = new AtomicBoolean(false); + + @Inject + public StartupRegistry(@StartupStage Set<ExceptionalCommand> startupActions) { + this.startupActions = Preconditions.checkNotNull(startupActions); + } + + @Override + public void execute() throws Exception { + if (!started.compareAndSet(false, true)) { + LOG.warning("Startup actions cannot be executed more than once, ignoring."); + } + + for (ExceptionalCommand<?> startupAction : startupActions) { + startupAction.execute(); + } + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/StartupStage.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/StartupStage.java b/commons/src/main/java/org/apache/aurora/common/application/StartupStage.java new file mode 100644 index 0000000..8050901 --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/StartupStage.java @@ -0,0 +1,31 @@ +/** + * 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 org.apache.aurora.common.application; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import com.google.inject.BindingAnnotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Binding annotation used for the startup registry. + */ +@BindingAnnotation +@Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME) +public @interface StartupStage { } http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/http/DefaultQuitHandler.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/http/DefaultQuitHandler.java b/commons/src/main/java/org/apache/aurora/common/application/http/DefaultQuitHandler.java new file mode 100644 index 0000000..6a48b84 --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/http/DefaultQuitHandler.java @@ -0,0 +1,43 @@ +/** + * 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 org.apache.aurora.common.application.http; + +import java.util.logging.Logger; + +import com.google.inject.Inject; + +import org.apache.aurora.common.application.Lifecycle; + +/** + * The default quit handler to use, which invokes {@link Lifecycle#shutdown()}. + * + * @author William Farner + */ +public class DefaultQuitHandler implements Runnable { + + private static final Logger LOG = Logger.getLogger(DefaultQuitHandler.class.getName()); + + private final Lifecycle lifecycle; + + @Inject + public DefaultQuitHandler(Lifecycle lifecycle) { + this.lifecycle = lifecycle; + } + + @Override + public void run() { + LOG.info("Instructing lifecycle to destroy."); + lifecycle.shutdown(); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/http/GraphViewer.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/http/GraphViewer.java b/commons/src/main/java/org/apache/aurora/common/application/http/GraphViewer.java new file mode 100644 index 0000000..f786f9e --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/http/GraphViewer.java @@ -0,0 +1,50 @@ +/** + * 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 org.apache.aurora.common.application.http; + +import com.google.inject.Binder; + +/** + * A utility class to register the file resources for the graph viewer. + */ +public final class GraphViewer { + + private GraphViewer() { + // Utility class. + } + + private static void registerJs(Binder binder, String assetName) { + Registration.registerHttpAsset( + binder, + "/graphview/" + assetName, + GraphViewer.class, + "graphview/" + assetName, + "application/javascript", + true); + } + + /** + * Registers required resources with the binder. + * + * @param binder Binder to register with. + */ + public static void registerResources(Binder binder) { + registerJs(binder, "dygraph-combined.js"); + registerJs(binder, "dygraph-extra.js"); + registerJs(binder, "grapher.js"); + registerJs(binder, "parser.js"); + Registration.registerHttpAsset(binder, + "/graphview", GraphViewer.class, "graphview/graphview.html", "text/html", false); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/http/HttpAssetConfig.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/http/HttpAssetConfig.java b/commons/src/main/java/org/apache/aurora/common/application/http/HttpAssetConfig.java new file mode 100644 index 0000000..e2160e7 --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/http/HttpAssetConfig.java @@ -0,0 +1,51 @@ +/** + * 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 org.apache.aurora.common.application.http; + +import java.net.URL; + +import com.google.common.io.Resources; + +import org.apache.aurora.common.net.http.handlers.AssetHandler; +import org.apache.aurora.common.net.http.handlers.AssetHandler.StaticAsset; + +import static org.apache.aurora.common.base.MorePreconditions.checkNotBlank; + +/** + * Configuration for a static HTTP-served asset. + * + * TODO(William Farner): Move this to a more appropriate package after initial AppLauncher check-in. + * + * @author William Farner + */ +public class HttpAssetConfig { + public final String path; + public final AssetHandler handler; + public final boolean silent; + + /** + * Creates a new asset configuration. + * + * @param path HTTP path the asset should be accessible from. + * @param asset Asset resource URL. + * @param contentType HTTP content-type to report for the asset. + * @param silent Whether the asset should be visible on the default index page. + */ + public HttpAssetConfig(String path, URL asset, String contentType, boolean silent) { + this.path = checkNotBlank(path); + this.handler = new AssetHandler( + new StaticAsset(Resources.newInputStreamSupplier(asset), contentType, true)); + this.silent = silent; + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/http/HttpFilterConfig.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/http/HttpFilterConfig.java b/commons/src/main/java/org/apache/aurora/common/application/http/HttpFilterConfig.java new file mode 100644 index 0000000..d096317 --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/http/HttpFilterConfig.java @@ -0,0 +1,39 @@ +/** + * 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 org.apache.aurora.common.application.http; + +import javax.servlet.Filter; + +import org.apache.aurora.common.base.MorePreconditions; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Configuration tuple for an HTTP filter. + */ +public class HttpFilterConfig { + public final Class<? extends Filter> filterClass; + public final String pathSpec; + + /** + * Creates a new filter configuration. + * + * @param filterClass Filter class. + * @param pathSpec Path spec that the filter should match. + */ + public HttpFilterConfig(Class<? extends Filter> filterClass, String pathSpec) { + this.pathSpec = MorePreconditions.checkNotBlank(pathSpec); + this.filterClass = checkNotNull(filterClass); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/http/HttpServletConfig.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/http/HttpServletConfig.java b/commons/src/main/java/org/apache/aurora/common/application/http/HttpServletConfig.java new file mode 100644 index 0000000..4444e4c --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/http/HttpServletConfig.java @@ -0,0 +1,65 @@ +/** + * 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 org.apache.aurora.common.application.http; + +import javax.servlet.http.HttpServlet; + +import com.google.common.collect.ImmutableMap; + +import static com.google.common.base.Preconditions.checkNotNull; + +import static org.apache.aurora.common.base.MorePreconditions.checkNotBlank; + +/** + * An {@link javax.servlet.http.HttpServlet} configuration used to mount HTTP handlers via + * {@link Registration#registerServlet(com.google.inject.Binder, HttpServletConfig)}. + * + * TODO(William Farner): Move this to a more appropriate package after initial AppLauncher check-in. + * + */ +public class HttpServletConfig { + public final String path; + public final Class<? extends HttpServlet> handlerClass; + public final ImmutableMap<String, String> params; + public final boolean silent; + + /** + * Creates a new servlet config. + * + * @param path the absolute path to mount the handler on + * @param servletClass the type of servlet that will render pages at {@code path} + * @param silent whether or not to display a link for this handler on the landing page + */ + public HttpServletConfig(String path, Class<? extends HttpServlet> servletClass, + boolean silent) { + this(path, servletClass, ImmutableMap.<String, String>of(), silent); + } + + /** + * Registers a new servlet config with servlet initialization parameters. + * + * @param path the absolute path to mount the handler on + * @param servletClass the type of servlet that will render pages at {@code path} + * @param params a map of servlet init parameters to initialize the servlet with + * @param silent whether or not to display a link for this handler on the landing page + */ + public HttpServletConfig(String path, Class<? extends HttpServlet> servletClass, + ImmutableMap<String, String> params, boolean silent) { + + this.path = checkNotBlank(path); + this.handlerClass = checkNotNull(servletClass); + this.params = checkNotNull(params); + this.silent = silent; + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/http/Registration.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/http/Registration.java b/commons/src/main/java/org/apache/aurora/common/application/http/Registration.java new file mode 100644 index 0000000..b66c8c8 --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/http/Registration.java @@ -0,0 +1,155 @@ +/** + * 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 org.apache.aurora.common.application.http; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.net.URL; + +import javax.servlet.Filter; +import javax.servlet.http.HttpServlet; + +import com.google.common.io.Resources; +import com.google.inject.Binder; +import com.google.inject.BindingAnnotation; +import com.google.inject.multibindings.Multibinder; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Utility class for registering HTTP servlets and assets. + */ +public final class Registration { + + private Registration() { + // Utility class. + } + + /** + * Equivalent to + * {@code registerServlet(binder, new HttpServletConfig(path, servletClass, silent))}. + */ + public static void registerServlet(Binder binder, String path, + Class<? extends HttpServlet> servletClass, boolean silent) { + registerServlet(binder, new HttpServletConfig(path, servletClass, silent)); + } + + /** + * Registers a binding for an {@link javax.servlet.http.HttpServlet} to be exported at a specified + * path. + * + * @param binder a guice binder to register the handler with + * @param config a servlet mounting specification + */ + public static void registerServlet(Binder binder, HttpServletConfig config) { + Multibinder.newSetBinder(binder, HttpServletConfig.class).addBinding().toInstance(config); + } + + /** + * A binding annotation applied to the set of additional index page links bound via + * {@link #Registration#registerEndpoint()} + */ + @BindingAnnotation + @Target({FIELD, PARAMETER, METHOD}) + @Retention(RUNTIME) + public @interface IndexLink { } + + /** + * Gets the multibinder used to bind links on the root servlet. + * The resulting {@link java.util.Set} is bound with the {@link IndexLink} annotation. + * + * @param binder a guice binder to associate the multibinder with. + * @return The multibinder to bind index links against. + */ + public static Multibinder<String> getEndpointBinder(Binder binder) { + return Multibinder.newSetBinder(binder, String.class, IndexLink.class); + } + + /** + * Registers a link to display on the root servlet. + * + * @param binder a guice binder to register the link with. + * @param endpoint Endpoint URI to include. + */ + public static void registerEndpoint(Binder binder, String endpoint) { + getEndpointBinder(binder).addBinding().toInstance(endpoint); + } + + /** + * Registers a binding for a URL asset to be served by the HTTP server, with an optional + * entity tag for cache control. + * + * @param binder a guice binder to register the handler with + * @param servedPath Path to serve the resource from in the HTTP server. + * @param asset Resource to be served. + * @param assetType MIME-type for the asset. + * @param silent Whether the server should hide this asset on the index page. + */ + public static void registerHttpAsset(Binder binder, String servedPath, URL asset, + String assetType, boolean silent) { + Multibinder.newSetBinder(binder, HttpAssetConfig.class).addBinding().toInstance( + new HttpAssetConfig(servedPath, asset, assetType, silent)); + } + + /** + * Registers a binding for a classpath resource to be served by the HTTP server, using a resource + * path relative to a class. + * + * @param binder a guice binder to register the handler with + * @param servedPath Path to serve the asset from in the HTTP server. + * @param contextClass Context class for defining the relative path to the asset. + * @param assetRelativePath Path to the served asset, relative to {@code contextClass}. + * @param assetType MIME-type for the asset. + * @param silent Whether the server should hide this asset on the index page. + */ + public static void registerHttpAsset( + Binder binder, + String servedPath, + Class<?> contextClass, + String assetRelativePath, + String assetType, + boolean silent) { + + registerHttpAsset(binder, servedPath, Resources.getResource(contextClass, assetRelativePath), + assetType, silent); + } + + /** + * Gets the multibinder used to bind HTTP filters. + * + * @param binder a guice binder to associate the multibinder with. + * @return The multibinder to bind HTTP filter configurations against. + */ + public static Multibinder<HttpFilterConfig> getFilterBinder(Binder binder) { + return Multibinder.newSetBinder(binder, HttpFilterConfig.class); + } + + /** + * Registers an HTTP servlet filter. + * + * @param binder a guice binder to register the filter with. + * @param filterClass Filter class to register. + * @param pathSpec Path spec that the filter should be activated on. + */ + public static void registerServletFilter( + Binder binder, + Class<? extends Filter> filterClass, + String pathSpec) { + + getFilterBinder(binder).addBinding().toInstance(new HttpFilterConfig(filterClass, pathSpec)); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/AppLauncherModule.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/AppLauncherModule.java b/commons/src/main/java/org/apache/aurora/common/application/modules/AppLauncherModule.java new file mode 100644 index 0000000..65e9250 --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/modules/AppLauncherModule.java @@ -0,0 +1,52 @@ +/** + * 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 org.apache.aurora.common.application.modules; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.inject.AbstractModule; +import com.google.inject.Singleton; + +import org.apache.aurora.common.stats.Stats; +import org.apache.aurora.common.util.BuildInfo; + +import org.apache.aurora.common.application.AppLauncher; + +/** + * Binding module for the bare minimum requirements for the + * {@link AppLauncher}. + * + * @author William Farner + */ +public class AppLauncherModule extends AbstractModule { + + private static final Logger LOG = Logger.getLogger(AppLauncherModule.class.getName()); + private static final AtomicLong UNCAUGHT_EXCEPTIONS = Stats.exportLong("uncaught_exceptions"); + + @Override + protected void configure() { + bind(BuildInfo.class).in(Singleton.class); + bind(UncaughtExceptionHandler.class).to(LoggingExceptionHandler.class); + } + + public static class LoggingExceptionHandler implements UncaughtExceptionHandler { + @Override public void uncaughtException(Thread t, Throwable e) { + UNCAUGHT_EXCEPTIONS.incrementAndGet(); + LOG.log(Level.SEVERE, "Uncaught exception from " + t + ":" + e, e); + } + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/LifecycleModule.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/LifecycleModule.java b/commons/src/main/java/org/apache/aurora/common/application/modules/LifecycleModule.java new file mode 100644 index 0000000..b3e59e7 --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/modules/LifecycleModule.java @@ -0,0 +1,194 @@ +/** + * 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 org.apache.aurora.common.application.modules; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import com.google.common.annotations.VisibleForTesting; +import com.google.inject.AbstractModule; +import com.google.inject.Binder; +import com.google.inject.BindingAnnotation; +import com.google.inject.Inject; +import com.google.inject.Key; +import com.google.inject.Singleton; +import com.google.inject.multibindings.Multibinder; + +import org.apache.aurora.common.application.Lifecycle; +import org.apache.aurora.common.application.ShutdownRegistry; +import org.apache.aurora.common.application.ShutdownStage; +import org.apache.aurora.common.application.StartupRegistry; +import org.apache.aurora.common.application.StartupStage; + +import org.apache.aurora.common.base.Command; +import org.apache.aurora.common.base.ExceptionalCommand; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Binding module for startup and shutdown controller and registries. + * + * Bindings provided by this module: + * <ul> + * <li>{@code @StartupStage ExceptionalCommand} - Command to execute all startup actions. + * <li>{@code ShutdownRegistry} - Registry for adding shutdown actions. + * <li>{@code @ShutdownStage Command} - Command to execute all shutdown commands. + * </ul> + * + * If you would like to register a startup action that starts a local network service, please + * consider using {@link LocalServiceRegistry}. + * + * @author William Farner + */ +public class LifecycleModule extends AbstractModule { + + /** + * Binding annotation used for local services. + * This is used to ensure the LocalService bindings are visibile within the package only, to + * prevent injection inadvertently triggering a service launch. + */ + @BindingAnnotation + @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME) + @interface Service { } + + @Override + protected void configure() { + bind(Lifecycle.class).in(Singleton.class); + + bind(Key.get(ExceptionalCommand.class, StartupStage.class)).to(StartupRegistry.class); + bind(StartupRegistry.class).in(Singleton.class); + + bind(ShutdownRegistry.class).to(ShutdownRegistry.ShutdownRegistryImpl.class); + bind(Key.get(Command.class, ShutdownStage.class)).to(ShutdownRegistry.ShutdownRegistryImpl.class); + bind(ShutdownRegistry.ShutdownRegistryImpl.class).in(Singleton.class); + bindStartupAction(binder(), ShutdownHookRegistration.class); + + bind(LocalServiceRegistry.class).in(Singleton.class); + + // Ensure that there is at least an empty set for the service runners. + runnerBinder(binder()); + + bindStartupAction(binder(), LocalServiceLauncher.class); + } + + /** + * Thrown when a local service fails to launch. + */ + public static class LaunchException extends Exception { + public LaunchException(String msg) { + super(msg); + } + + public LaunchException(String msg, Throwable cause) { + super(msg, cause); + } + } + + /** + * Responsible for starting and stopping a local service. + */ + public interface ServiceRunner { + + /** + * Launches the local service. + * + * @return Information about the launched service. + * @throws LaunchException If the service failed to launch. + */ + LocalServiceRegistry.LocalService launch() throws LaunchException; + } + + @VisibleForTesting + static Multibinder<ServiceRunner> runnerBinder(Binder binder) { + return Multibinder.newSetBinder(binder, ServiceRunner.class, Service.class); + } + + /** + * Binds a service runner that will start and stop a local service. + * + * @param binder Binder to bind against. + * @param launcher Launcher class for a service. + */ + public static void bindServiceRunner(Binder binder, Class<? extends ServiceRunner> launcher) { + runnerBinder(binder).addBinding().to(launcher); + binder.bind(launcher).in(Singleton.class); + } + + /** + * Binds a local service instance, without attaching an explicit lifecycle. + * + * @param binder Binder to bind against. + * @param service Local service instance to bind. + */ + public static void bindLocalService(Binder binder, final LocalServiceRegistry.LocalService service) { + runnerBinder(binder).addBinding().toInstance( + new ServiceRunner() { + @Override public LocalServiceRegistry.LocalService launch() { + return service; + } + }); + } + + /** + * Adds a startup action to the startup registry binding. + * + * @param binder Binder to bind against. + * @param actionClass Class to bind (and instantiate via guice) for execution at startup. + */ + public static void bindStartupAction(Binder binder, + Class<? extends ExceptionalCommand> actionClass) { + + Multibinder.newSetBinder(binder, ExceptionalCommand.class, StartupStage.class) + .addBinding().to(actionClass); + } + + /** + * Startup command to register the shutdown registry as a process shutdown hook. + */ + private static class ShutdownHookRegistration implements Command { + private final Command shutdownCommand; + + @Inject ShutdownHookRegistration(@ShutdownStage Command shutdownCommand) { + this.shutdownCommand = checkNotNull(shutdownCommand); + } + + @Override public void execute() { + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + @Override public void run() { + shutdownCommand.execute(); + } + }, "ShutdownRegistry-Hook")); + } + } + + /** + * Startup command that ensures startup and shutdown of local services. + */ + private static class LocalServiceLauncher implements Command { + private final LocalServiceRegistry serviceRegistry; + + @Inject LocalServiceLauncher(LocalServiceRegistry serviceRegistry) { + this.serviceRegistry = checkNotNull(serviceRegistry); + } + + @Override public void execute() { + serviceRegistry.ensureLaunched(); + } + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/LocalServiceRegistry.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/LocalServiceRegistry.java b/commons/src/main/java/org/apache/aurora/common/application/modules/LocalServiceRegistry.java new file mode 100644 index 0000000..027e4a7 --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/modules/LocalServiceRegistry.java @@ -0,0 +1,258 @@ +/** + * 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 org.apache.aurora.common.application.modules; + +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; +import com.google.inject.Inject; +import com.google.inject.Provider; + +import org.apache.aurora.common.application.ShutdownRegistry; +import org.apache.commons.lang.builder.ToStringBuilder; + +import org.apache.aurora.common.application.modules.LifecycleModule.LaunchException; +import org.apache.aurora.common.application.modules.LifecycleModule.Service; +import org.apache.aurora.common.application.modules.LifecycleModule.ServiceRunner; +import org.apache.aurora.common.base.Command; +import org.apache.aurora.common.base.MorePreconditions; +import org.apache.aurora.common.net.InetSocketAddressHelper; + +/** + * Registry for services that should be exported from the application. + * + * Example of announcing and registering a port: + * <pre> + * class MyLauncher implements Provider<LocalService> { + * public LocalService get() { + * // Launch service. + * } + * } + * + * class MyServiceModule extends AbstractModule { + * public void configure() { + * LifeCycleModule.bindServiceLauncher(binder(), MyLauncher.class); + * } + * } + * </pre> + */ +public class LocalServiceRegistry { + + private static final Predicate<LocalService> IS_PRIMARY = new Predicate<LocalService>() { + @Override public boolean apply(LocalService service) { + return service.primary; + } + }; + + private static final Function<LocalService, InetSocketAddress> SERVICE_TO_SOCKET = + new Function<LocalService, InetSocketAddress>() { + @Override public InetSocketAddress apply(LocalService service) { + try { + return InetSocketAddressHelper.getLocalAddress(service.port); + } catch (UnknownHostException e) { + throw new RuntimeException("Failed to resolve local address for " + service, e); + } + } + }; + + private static final Function<LocalService, String> GET_NAME = + new Function<LocalService, String>() { + @Override public String apply(LocalService service) { + return Iterables.getOnlyElement(service.names); + } + }; + + private final ShutdownRegistry shutdownRegistry; + private final Provider<Set<ServiceRunner>> runnerProvider; + + private Optional<InetSocketAddress> primarySocket = null; + private Map<String, InetSocketAddress> auxiliarySockets = null; + + /** + * Creates a new local service registry. + * + * @param runnerProvider provider of registered local services. + * @param shutdownRegistry Shutdown registry to tear down launched services. + */ + @Inject + public LocalServiceRegistry(@Service Provider<Set<ServiceRunner>> runnerProvider, + ShutdownRegistry shutdownRegistry) { + this.runnerProvider = Preconditions.checkNotNull(runnerProvider); + this.shutdownRegistry = Preconditions.checkNotNull(shutdownRegistry); + } + + private static final Function<LocalService, Iterable<LocalService>> AUX_NAME_BREAKOUT = + new Function<LocalService, Iterable<LocalService>>() { + @Override public Iterable<LocalService> apply(final LocalService service) { + Preconditions.checkArgument(!service.primary); + Function<String, LocalService> oneNameService = new Function<String, LocalService>() { + @Override public LocalService apply(String name) { + return LocalService.auxiliaryService(name, service.port, service.shutdownCommand); + } + }; + return Iterables.transform(service.names, oneNameService); + } + }; + + /** + * Launches the local services if not already launched, otherwise this is a no-op. + */ + void ensureLaunched() { + if (primarySocket == null) { + ImmutableList.Builder<LocalService> builder = ImmutableList.builder(); + + for (ServiceRunner runner : runnerProvider.get()) { + try { + LocalService service = runner.launch(); + builder.add(service); + shutdownRegistry.addAction(service.shutdownCommand); + } catch (LaunchException e) { + throw new IllegalStateException("Failed to launch " + runner, e); + } + } + + List<LocalService> localServices = builder.build(); + Iterable<LocalService> primaries = Iterables.filter(localServices, IS_PRIMARY); + switch (Iterables.size(primaries)) { + case 0: + primarySocket = Optional.absent(); + break; + + case 1: + primarySocket = Optional.of(SERVICE_TO_SOCKET.apply(Iterables.getOnlyElement(primaries))); + break; + + default: + throw new IllegalArgumentException("More than one primary local service: " + primaries); + } + + Iterable<LocalService> auxSinglyNamed = Iterables.concat( + FluentIterable.from(localServices) + .filter(Predicates.not(IS_PRIMARY)) + .transform(AUX_NAME_BREAKOUT)); + + Map<String, LocalService> byName; + try { + byName = Maps.uniqueIndex(auxSinglyNamed, GET_NAME); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Auxiliary services with identical names.", e); + } + + auxiliarySockets = ImmutableMap.copyOf(Maps.transformValues(byName, SERVICE_TO_SOCKET)); + } + } + + /** + * Gets the mapping from auxiliary port name to socket. + * + * @return Auxiliary port mapping. + */ + public synchronized Map<String, InetSocketAddress> getAuxiliarySockets() { + ensureLaunched(); + return auxiliarySockets; + } + + /** + * Gets the optional primary socket address, and returns an unresolved local socket address + * representing that port. + * + * @return Local socket address for the primary port. + * @throws IllegalStateException If the primary port was not set. + */ + public synchronized Optional<InetSocketAddress> getPrimarySocket() { + ensureLaunched(); + return primarySocket; + } + + /** + * An individual local service. + */ + public static final class LocalService { + private final boolean primary; + private final Set<String> names; + private final int port; + private final Command shutdownCommand; + + private LocalService(boolean primary, Set<String> names, int port, + Command shutdownCommand) { + this.primary = primary; + this.names = names; + this.port = port; + this.shutdownCommand = Preconditions.checkNotNull(shutdownCommand); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("primary", primary) + .append("name", names) + .append("port", port) + .toString(); + } + + /** + * Creates a primary local service. + * + * @param port Service port. + * @param shutdownCommand A command that will shut down the service. + * @return A new primary local service. + */ + public static LocalService primaryService(int port, Command shutdownCommand) { + return new LocalService(true, ImmutableSet.<String>of(), port, shutdownCommand); + } + + /** + * Creates a named auxiliary service. + * + * @param name Service name. + * @param port Service port. + * @param shutdownCommand A command that will shut down the service. + * @return A new auxiliary local service. + */ + public static LocalService auxiliaryService(String name, int port, Command shutdownCommand) { + return auxiliaryService(ImmutableSet.of(name), port, shutdownCommand); + } + + /** + * Creates an auxiliary service identified by multiple names. + * + * @param names Service names. + * @param port Service port. + * @param shutdownCommand A command that will shut down the service. + * @return A new auxiliary local service. + */ + public static LocalService auxiliaryService( + Set<String> names, + int port, + Command shutdownCommand) { + + MorePreconditions.checkNotBlank(names); + return new LocalService(false, names, port, shutdownCommand); + } + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/LogModule.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/LogModule.java b/commons/src/main/java/org/apache/aurora/common/application/modules/LogModule.java new file mode 100644 index 0000000..107e3aa --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/modules/LogModule.java @@ -0,0 +1,117 @@ +/** + * 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 org.apache.aurora.common.application.modules; + +import java.io.File; +import java.util.logging.Logger; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.inject.AbstractModule; +import com.google.inject.Inject; +import com.google.inject.TypeLiteral; +import com.google.inject.name.Named; +import com.google.inject.name.Names; + +import org.apache.aurora.common.args.Arg; +import org.apache.aurora.common.args.CmdLine; +import org.apache.aurora.common.args.constraints.CanRead; +import org.apache.aurora.common.args.constraints.Exists; +import org.apache.aurora.common.args.constraints.IsDirectory; +import org.apache.aurora.common.base.Command; +import org.apache.aurora.common.logging.LogUtil; +import org.apache.aurora.common.logging.RootLogConfig; +import org.apache.aurora.common.logging.RootLogConfig.Configuration; +import org.apache.aurora.common.net.http.handlers.LogPrinter; +import org.apache.aurora.common.stats.StatImpl; +import org.apache.aurora.common.stats.Stats; + +/** + * Binding module for logging-related bindings, such as the log directory. + * + * This module uses a single optional command line argument 'log_dir'. If unset, the logging + * directory will be auto-discovered via: + * {@link LogUtil#getLogManagerLogDir()}. + * + * Bindings provided by this module: + * <ul> + * <li>{@code @Named(LogPrinter.LOG_DIR_KEY) File} - Log directory. + * <li>{@code Optional<RootLogConfig.Configuraton>} - If glog is enabled the configuration + * used. + * </ul> + * + * Default bindings that may be overridden: + * <ul> + * <li>Log directory: directory where application logs are written. May be overridden by binding + * to: {@code bind(File.class).annotatedWith(Names.named(LogPrinter.LOG_DIR_KEY))}. + * </ul> + * + * @author William Farner + */ +public class LogModule extends AbstractModule { + + private static final Logger LOG = Logger.getLogger(LogModule.class.getName()); + + @Exists + @CanRead + @IsDirectory + @CmdLine(name = "log_dir", + help = "The directory where application logs are written.") + private static final Arg<File> LOG_DIR = Arg.create(null); + + @CmdLine(name = "use_glog", + help = "True to use the new glog-based configuration for the root logger.") + private static final Arg<Boolean> USE_GLOG = Arg.create(true); + + @Override + protected void configure() { + // Bind the default log directory. + bind(File.class).annotatedWith(Names.named(LogPrinter.LOG_DIR_KEY)).toInstance(getLogDir()); + + LifecycleModule.bindStartupAction(binder(), ExportLogDir.class); + + Configuration configuration = null; + if (USE_GLOG.get()) { + configuration = RootLogConfig.configurationFromFlags(); + configuration.apply(); + } + bind(new TypeLiteral<Optional<Configuration>>() { }) + .toInstance(Optional.fromNullable(configuration)); + } + + private File getLogDir() { + File logDir = LOG_DIR.get(); + if (logDir == null) { + logDir = LogUtil.getLogManagerLogDir(); + LOG.info("From logging properties, parsed log directory " + logDir.getAbsolutePath()); + } + return logDir; + } + + public static final class ExportLogDir implements Command { + private final File logDir; + + @Inject ExportLogDir(@Named(LogPrinter.LOG_DIR_KEY) final File logDir) { + this.logDir = Preconditions.checkNotNull(logDir); + } + + @Override public void execute() { + Stats.exportStatic(new StatImpl<String>("logging_dir") { + @Override public String read() { + return logDir.getAbsolutePath(); + } + }); + } + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/StatsExportModule.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/StatsExportModule.java b/commons/src/main/java/org/apache/aurora/common/application/modules/StatsExportModule.java new file mode 100644 index 0000000..ba93d4c --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/modules/StatsExportModule.java @@ -0,0 +1,85 @@ +/** + * 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 org.apache.aurora.common.application.modules; + +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.google.inject.AbstractModule; +import com.google.inject.Inject; +import com.google.inject.Key; +import com.google.inject.TypeLiteral; + +import org.apache.aurora.common.application.ShutdownRegistry; +import org.apache.aurora.common.args.Arg; +import org.apache.aurora.common.args.CmdLine; +import org.apache.aurora.common.base.Closure; +import org.apache.aurora.common.base.Command; +import org.apache.aurora.common.quantity.Amount; +import org.apache.aurora.common.quantity.Time; +import org.apache.aurora.common.stats.NumericStatExporter; + +/** + * Module to enable periodic exporting of registered stats to an external service. + * + * This modules supports a single command line argument, {@code stat_export_interval}, which + * controls the export interval (defaulting to 1 minute). + * + * Bindings required by this module: + * <ul> + * <li>{@code @ShutdownStage ShutdownRegistry} - Shutdown action registry. + * </ul> + * + * @author William Farner + */ +public class StatsExportModule extends AbstractModule { + + @CmdLine(name = "stat_export_interval", + help = "Amount of time to wait between stat exports.") + private static final Arg<Amount<Long, Time>> EXPORT_INTERVAL = + Arg.create(Amount.of(1L, Time.MINUTES)); + + @Override + protected void configure() { + requireBinding(Key.get(new TypeLiteral<Closure<Map<String, ? extends Number>>>() { })); + LifecycleModule.bindStartupAction(binder(), StartCuckooExporter.class); + } + + public static final class StartCuckooExporter implements Command { + + private final Closure<Map<String, ? extends Number>> statSink; + private final ShutdownRegistry shutdownRegistry; + + @Inject StartCuckooExporter( + Closure<Map<String, ? extends Number>> statSink, + ShutdownRegistry shutdownRegistry) { + + this.statSink = Preconditions.checkNotNull(statSink); + this.shutdownRegistry = Preconditions.checkNotNull(shutdownRegistry); + } + + @Override public void execute() { + ThreadFactory threadFactory = + new ThreadFactoryBuilder().setNameFormat("CuckooExporter-%d").setDaemon(true).build(); + + final NumericStatExporter exporter = new NumericStatExporter(statSink, + Executors.newScheduledThreadPool(1, threadFactory), EXPORT_INTERVAL.get()); + + exporter.start(shutdownRegistry); + } + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/StatsModule.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/StatsModule.java b/commons/src/main/java/org/apache/aurora/common/application/modules/StatsModule.java new file mode 100644 index 0000000..78d9512 --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/modules/StatsModule.java @@ -0,0 +1,146 @@ +/** + * 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 org.apache.aurora.common.application.modules; + +import java.util.Properties; +import java.util.logging.Logger; + +import com.google.common.base.Supplier; +import com.google.common.primitives.Longs; +import com.google.inject.AbstractModule; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.google.inject.TypeLiteral; +import com.google.inject.name.Names; + +import org.apache.aurora.common.application.ShutdownRegistry; +import org.apache.aurora.common.args.Arg; +import org.apache.aurora.common.args.CmdLine; +import org.apache.aurora.common.base.Command; +import org.apache.aurora.common.quantity.Amount; +import org.apache.aurora.common.quantity.Time; +import org.apache.aurora.common.stats.JvmStats; +import org.apache.aurora.common.stats.Stat; +import org.apache.aurora.common.stats.StatImpl; +import org.apache.aurora.common.stats.StatRegistry; +import org.apache.aurora.common.stats.Stats; +import org.apache.aurora.common.stats.TimeSeriesRepository; +import org.apache.aurora.common.stats.TimeSeriesRepositoryImpl; +import org.apache.aurora.common.util.BuildInfo; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Binding module for injections related to the in-process stats system. + * + * This modules supports two command line arguments: + * <ul> + * <li>{@code stat_sampling_interval} - Statistic value sampling interval. + * <li>{@code stat_retention_period} - Time for a stat to be retained in memory before expring. + * </ul> + * + * Bindings required by this module: + * <ul> + * <li>{@code ShutdownRegistry} - Shutdown hook registry. + * <li>{@code BuildInfo} - Build information for the application. + * </ul> + * + * @author William Farner + */ +public class StatsModule extends AbstractModule { + + @CmdLine(name = "stat_sampling_interval", help = "Statistic value sampling interval.") + private static final Arg<Amount<Long, Time>> SAMPLING_INTERVAL = + Arg.create(Amount.of(1L, Time.SECONDS)); + + @CmdLine(name = "stat_retention_period", + help = "Time for a stat to be retained in memory before expiring.") + private static final Arg<Amount<Long, Time>> RETENTION_PERIOD = + Arg.create(Amount.of(1L, Time.HOURS)); + + public static Amount<Long, Time> getSamplingInterval() { + return SAMPLING_INTERVAL.get(); + } + + @Override + protected void configure() { + requireBinding(ShutdownRegistry.class); + requireBinding(BuildInfo.class); + + // Bindings for TimeSeriesRepositoryImpl. + bind(StatRegistry.class).toInstance(Stats.STAT_REGISTRY); + bind(new TypeLiteral<Amount<Long, Time>>() { }) + .annotatedWith(Names.named(TimeSeriesRepositoryImpl.SAMPLE_RETENTION_PERIOD)) + .toInstance(RETENTION_PERIOD.get()); + bind(new TypeLiteral<Amount<Long, Time>>() { }) + .annotatedWith(Names.named(TimeSeriesRepositoryImpl.SAMPLE_PERIOD)) + .toInstance(SAMPLING_INTERVAL.get()); + bind(TimeSeriesRepository.class).to(TimeSeriesRepositoryImpl.class).in(Singleton.class); + + bind(new TypeLiteral<Supplier<Iterable<Stat<?>>>>() { }).toInstance( + new Supplier<Iterable<Stat<?>>>() { + @Override public Iterable<Stat<?>> get() { + return Stats.getVariables(); + } + } + ); + + LifecycleModule.bindStartupAction(binder(), StartStatPoller.class); + } + + public static final class StartStatPoller implements Command { + private static final Logger LOG = Logger.getLogger(StartStatPoller.class.getName()); + private final ShutdownRegistry shutdownRegistry; + private final BuildInfo buildInfo; + private final TimeSeriesRepository timeSeriesRepository; + + @Inject StartStatPoller( + ShutdownRegistry shutdownRegistry, + BuildInfo buildInfo, + TimeSeriesRepository timeSeriesRepository) { + + this.shutdownRegistry = checkNotNull(shutdownRegistry); + this.buildInfo = checkNotNull(buildInfo); + this.timeSeriesRepository = checkNotNull(timeSeriesRepository); + } + + @Override public void execute() { + Properties properties = buildInfo.getProperties(); + LOG.info("Build information: " + properties); + for (String name : properties.stringPropertyNames()) { + final String stringValue = properties.getProperty(name); + if (stringValue == null) { + continue; + } + final Long longValue = Longs.tryParse(stringValue); + if (longValue != null) { + Stats.exportStatic(new StatImpl<Long>(Stats.normalizeName(name)) { + @Override public Long read() { + return longValue; + } + }); + } else { + Stats.exportString(new StatImpl<String>(Stats.normalizeName(name)) { + @Override public String read() { + return stringValue; + } + }); + } + } + + JvmStats.export(); + timeSeriesRepository.start(shutdownRegistry); + } + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/ThriftModule.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/ThriftModule.java b/commons/src/main/java/org/apache/aurora/common/application/modules/ThriftModule.java new file mode 100644 index 0000000..55236df --- /dev/null +++ b/commons/src/main/java/org/apache/aurora/common/application/modules/ThriftModule.java @@ -0,0 +1,41 @@ +/** + * 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 org.apache.aurora.common.application.modules; + +import com.google.inject.AbstractModule; +import com.google.inject.multibindings.Multibinder; +import com.google.inject.name.Names; + +import org.apache.aurora.common.application.http.Registration; +import org.apache.aurora.common.net.http.handlers.ThriftServlet; +import org.apache.aurora.common.net.monitoring.TrafficMonitor; + +/** + * Binding module for thrift traffic monitor servlets, to ensure an empty set is available for + * the thrift traffic monitor servlet. + * + * @author William Farner + */ +public class ThriftModule extends AbstractModule { + @Override + protected void configure() { + // Make sure that there is at least an empty set bound to client andserver monitors. + Multibinder.newSetBinder(binder(), TrafficMonitor.class, + Names.named(ThriftServlet.THRIFT_CLIENT_MONITORS)); + Multibinder.newSetBinder(binder(), TrafficMonitor.class, + Names.named(ThriftServlet.THRIFT_SERVER_MONITORS)); + + Registration.registerServlet(binder(), "/thrift", ThriftServlet.class, false); + } +}
