Java tutorial
/* * 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.spotify.reaper; import java.util.Arrays; import java.util.EnumSet; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.servlet.DispatcherType; import javax.servlet.FilterRegistration; import org.apache.cassandra.repair.RepairParallelism; import org.eclipse.jetty.servlets.CrossOriginFilter; import org.flywaydb.core.Flyway; import org.joda.time.DateTimeZone; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.spotify.reaper.AppContext; import com.spotify.reaper.ReaperApplicationConfiguration; import com.spotify.reaper.ReaperApplicationConfiguration.JmxCredentials; import com.spotify.reaper.ReaperException; import com.spotify.reaper.cassandra.JmxConnectionFactory; import com.spotify.reaper.resources.ClusterResource; import com.spotify.reaper.resources.PingResource; import com.spotify.reaper.resources.ReaperHealthCheck; import com.spotify.reaper.resources.RepairRunResource; import com.spotify.reaper.resources.RepairScheduleResource; import com.spotify.reaper.service.AutoSchedulingManager; import com.spotify.reaper.service.RepairManager; import com.spotify.reaper.service.SchedulingManager; import com.spotify.reaper.storage.CassandraStorage; import com.spotify.reaper.storage.IStorage; import com.spotify.reaper.storage.MemoryStorage; import com.spotify.reaper.storage.PostgresStorage; import io.dropwizard.Application; import io.dropwizard.assets.AssetsBundle; import org.skife.jdbi.v2.DBI; import io.dropwizard.jdbi.DBIFactory; import io.dropwizard.setup.Bootstrap; import io.dropwizard.setup.Environment; import sun.misc.Signal; import sun.misc.SignalHandler; public class ReaperApplication extends Application<ReaperApplicationConfiguration> { static final Logger LOG = LoggerFactory.getLogger(ReaperApplication.class); private AppContext context; public ReaperApplication() { super(); LOG.info("default ReaperApplication constructor called"); this.context = new AppContext(); } @VisibleForTesting public ReaperApplication(AppContext context) { super(); LOG.info("ReaperApplication constructor called with custom AppContext"); this.context = context; } public static void main(String[] args) throws Exception { new ReaperApplication().run(args); } @Override public String getName() { return "cassandra-reaper"; } /** * Before a Dropwizard application can provide the command-line interface, parse a configuration * file, or run as a server, it must first go through a bootstrapping phase. You can add Bundles, * Commands, or register Jackson modules to allow you to include custom types as part of your * configuration class. */ @Override public void initialize(Bootstrap<ReaperApplicationConfiguration> bootstrap) { bootstrap.addBundle(new AssetsBundle("/assets/", "/webui", "index.html")); bootstrap.getObjectMapper().registerModule(new JavaTimeModule()); } @Override public void run(ReaperApplicationConfiguration config, Environment environment) throws Exception { // Using UTC times everywhere as default. Affects only Yoda time. DateTimeZone.setDefault(DateTimeZone.UTC); checkConfiguration(config); context.config = config; addSignalHandlers(); // SIGHUP, etc. LOG.info("initializing runner thread pool with {} threads", config.getRepairRunThreadCount()); context.repairManager = new RepairManager(); context.repairManager.initializeThreadPool(config.getRepairRunThreadCount(), config.getHangingRepairTimeoutMins(), TimeUnit.MINUTES, 30, TimeUnit.SECONDS); if (context.storage == null) { LOG.info("initializing storage of type: {}", config.getStorageType()); context.storage = initializeStorage(config, environment); } else { LOG.info("storage already given in context, not initializing a new one"); } if (context.jmxConnectionFactory == null) { LOG.info("no JMX connection factory given in context, creating default"); context.jmxConnectionFactory = new JmxConnectionFactory(); } // read jmx host/port mapping from config and provide to jmx con.factory Map<String, Integer> jmxPorts = config.getJmxPorts(); if (jmxPorts != null) { LOG.debug("using JMX ports mapping: {}", jmxPorts); context.jmxConnectionFactory.setJmxPorts(jmxPorts); } // Enable cross-origin requests for using external GUI applications. if (config.isEnableCrossOrigin() || System.getProperty("enableCrossOrigin") != null) { final FilterRegistration.Dynamic cors = environment.servlets().addFilter("crossOriginRequests", CrossOriginFilter.class); cors.setInitParameter("allowedOrigins", "*"); cors.setInitParameter("allowedHeaders", "X-Requested-With,Content-Type,Accept,Origin"); cors.setInitParameter("allowedMethods", "OPTIONS,GET,PUT,POST,DELETE,HEAD"); cors.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*"); } JmxCredentials jmxAuth = config.getJmxAuth(); if (jmxAuth != null) { LOG.debug("using specified JMX credentials for authentication"); context.jmxConnectionFactory.setJmxAuth(jmxAuth); } LOG.info("creating and registering health checks"); // Notice that health checks are registered under the admin application on /healthcheck final ReaperHealthCheck healthCheck = new ReaperHealthCheck(context); environment.healthChecks().register("reaper", healthCheck); LOG.info("creating resources and registering endpoints"); final PingResource pingResource = new PingResource(); environment.jersey().register(pingResource); final ClusterResource addClusterResource = new ClusterResource(context); environment.jersey().register(addClusterResource); final RepairRunResource addRepairRunResource = new RepairRunResource(context); environment.jersey().register(addRepairRunResource); final RepairScheduleResource addRepairScheduleResource = new RepairScheduleResource(context); environment.jersey().register(addRepairScheduleResource); Thread.sleep(1000); SchedulingManager.start(context); if (config.hasAutoSchedulingEnabled()) { LOG.debug("using specified configuration for auto scheduling: {}", config.getAutoScheduling()); AutoSchedulingManager.start(context); } LOG.info("resuming pending repair runs"); context.repairManager.resumeRunningRepairRuns(context); } private IStorage initializeStorage(ReaperApplicationConfiguration config, Environment environment) throws ReaperException { IStorage storage; if ("memory".equalsIgnoreCase(config.getStorageType())) { storage = new MemoryStorage(); } else if ("cassandra".equalsIgnoreCase(config.getStorageType())) { storage = new CassandraStorage(config, environment); } else if ("database".equalsIgnoreCase(config.getStorageType())) { // create DBI instance DBI jdbi; final DBIFactory factory = new DBIFactory(); jdbi = factory.build(environment, config.getDataSourceFactory(), "postgresql"); // instanciate store storage = new PostgresStorage(jdbi); initDatabase(config); } else { LOG.error("invalid storageType: {}", config.getStorageType()); throw new ReaperException("invalid storage type: " + config.getStorageType()); } Preconditions.checkState(storage.isStorageConnected(), "Failed to connect storage"); return storage; } private void checkConfiguration(ReaperApplicationConfiguration config) { LOG.debug("repairIntensity: {}", config.getRepairIntensity()); LOG.debug("incrementalRepair: {}", config.getIncrementalRepair()); LOG.debug("repairRunThreadCount: {}", config.getRepairRunThreadCount()); LOG.debug("segmentCount: {}", config.getSegmentCount()); LOG.debug("repairParallelism: {}", config.getRepairParallelism()); LOG.debug("hangingRepairTimeoutMins: {}", config.getHangingRepairTimeoutMins()); LOG.debug("jmxPorts: {}", config.getJmxPorts()); } public static void checkRepairParallelismString(String givenRepairParallelism) throws ReaperException { try { RepairParallelism.valueOf(givenRepairParallelism.toUpperCase()); } catch (java.lang.IllegalArgumentException ex) { throw new ReaperException("invalid repair parallelism given \"" + givenRepairParallelism + "\", must be one of: " + Arrays.toString(RepairParallelism.values()), ex); } } void reloadConfiguration() { // TODO: reload configuration, but how? LOG.warn("SIGHUP signal dropped, missing implementation for configuration reload"); } private void addSignalHandlers() { if (!System.getProperty("os.name").toLowerCase().contains("win")) { LOG.debug("adding signal handler for SIGHUP"); Signal.handle(new Signal("HUP"), new SignalHandler() { @Override public void handle(Signal signal) { LOG.info("received SIGHUP signal: {}", signal); reloadConfiguration(); } }); } } private void initDatabase(ReaperApplicationConfiguration config) throws ReaperException { Flyway flyway = new Flyway(); flyway.setDataSource(config.getDataSourceFactory().getUrl(), config.getDataSourceFactory().getUser(), config.getDataSourceFactory().getPassword()); if ("org.h2.Driver".equals(config.getDataSourceFactory().getDriverClass())) { flyway.setLocations("/db/h2"); } else { flyway.setLocations("/db/postgres"); } flyway.setBaselineOnMigrate(true); flyway.migrate(); } }