org.graylog2.bootstrap.ServerBootstrap Maven / Gradle / Ivy
/*
* Copyright (C) 2020 Graylog, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* .
*/
package org.graylog2.bootstrap;
import com.github.joschi.jadconfig.guice.NamedConfigParametersModule;
import com.github.rvesse.airline.annotations.Option;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.Service;
import com.google.common.util.concurrent.ServiceManager;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.ProvisionException;
import com.google.inject.TypeLiteral;
import com.google.inject.util.Types;
import org.graylog2.Configuration;
import org.graylog2.audit.AuditActor;
import org.graylog2.audit.AuditEventSender;
import org.graylog2.bindings.ConfigurationModule;
import org.graylog2.bootstrap.preflight.MongoDBPreflightCheck;
import org.graylog2.bootstrap.preflight.PreflightCheckException;
import org.graylog2.bootstrap.preflight.PreflightCheckService;
import org.graylog2.bootstrap.preflight.PreflightWebModule;
import org.graylog2.bootstrap.preflight.ServerPreflightChecksModule;
import org.graylog2.bootstrap.preflight.web.PreflightBoot;
import org.graylog2.cluster.leader.LeaderElectionService;
import org.graylog2.cluster.preflight.DataNodeProvisioningBindings;
import org.graylog2.configuration.IndexerDiscoveryModule;
import org.graylog2.configuration.PathConfiguration;
import org.graylog2.configuration.TLSProtocolsConfiguration;
import org.graylog2.migrations.Migration;
import org.graylog2.migrations.MigrationType;
import org.graylog2.plugin.Plugin;
import org.graylog2.plugin.ServerStatus;
import org.graylog2.plugin.Tools;
import org.graylog2.plugin.inputs.MessageInput;
import org.graylog2.plugin.system.NodeId;
import org.graylog2.shared.bindings.FreshInstallDetectionModule;
import org.graylog2.shared.bindings.GenericBindings;
import org.graylog2.shared.bindings.GenericInitializerBindings;
import org.graylog2.shared.bindings.GuiceInjectorHolder;
import org.graylog2.shared.bindings.IsDevelopmentBindings;
import org.graylog2.shared.bindings.ObjectMapperModule;
import org.graylog2.shared.bindings.SchedulerBindings;
import org.graylog2.shared.bindings.ServerStatusBindings;
import org.graylog2.shared.bindings.SharedPeriodicalBindings;
import org.graylog2.shared.bindings.ValidatorModule;
import org.graylog2.shared.initializers.ServiceManagerListener;
import org.graylog2.shared.plugins.ChainingClassLoader;
import org.graylog2.shared.security.CertificateRenewalBindings;
import org.graylog2.shared.security.SecurityBindings;
import org.graylog2.shared.system.activities.Activity;
import org.graylog2.shared.system.activities.ActivityWriter;
import org.graylog2.shared.system.stats.SystemStatsModule;
import org.jsoftbiz.utils.OS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.graylog2.audit.AuditEventTypes.NODE_STARTUP_COMPLETE;
import static org.graylog2.audit.AuditEventTypes.NODE_STARTUP_INITIATE;
import static org.graylog2.bootstrap.preflight.PreflightWebModule.FEATURE_FLAG_PREFLIGHT_WEB_ENABLED;
public abstract class ServerBootstrap extends CmdLineTool {
private static final Logger LOG = LoggerFactory.getLogger(ServerBootstrap.class);
private boolean isFreshInstallation;
protected ServerBootstrap(String commandName, Configuration configuration) {
super(commandName, configuration);
this.commandName = commandName;
}
@Option(name = {"-p", "--pidfile"}, description = "File containing the PID of Graylog")
private String pidFile = TMPDIR + FILE_SEPARATOR + "graylog.pid";
@Option(name = {"-np", "--no-pid-file"}, description = "Do not write a PID file (overrides -p/--pidfile)")
private boolean noPidFile = false;
protected abstract void startNodeRegistration(Injector injector);
public String getPidFile() {
return pidFile;
}
public boolean isNoPidFile() {
return noPidFile;
}
private boolean isFreshInstallation() {
return isFreshInstallation;
}
private void registerFreshInstallation() {
this.isFreshInstallation = true;
}
@Override
protected void beforeStart(TLSProtocolsConfiguration tlsProtocolsConfiguration, PathConfiguration pathConfiguration) {
super.beforeStart(tlsProtocolsConfiguration, pathConfiguration);
// Do not use a PID file if the user requested not to
if (!isNoPidFile()) {
savePidFile(getPidFile());
}
// This needs to run before the first SSLContext is instantiated,
// because it sets up the default SSLAlgorithmConstraints
applySecuritySettings(tlsProtocolsConfiguration);
// Set these early in the startup because netty's NativeLibraryUtil uses a static initializer
setNettyNativeDefaults(pathConfiguration);
}
@Override
protected void beforeInjectorCreation(Set plugins) {
runPreFlightChecks(plugins);
}
private void runPreFlightChecks(Set plugins) {
if (configuration.getSkipPreflightChecks()) {
LOG.info("Skipping preflight checks");
return;
}
runMongoPreflightCheck();
final List preflightCheckModules = plugins.stream().map(Plugin::preflightCheckModules)
.flatMap(Collection::stream).collect(Collectors.toList());
preflightCheckModules.add(new FreshInstallDetectionModule(isFreshInstallation()));
if (featureFlags.isOn(FEATURE_FLAG_PREFLIGHT_WEB_ENABLED)) {
runPreflightWeb(preflightCheckModules);
}
final Injector preflightInjector = getPreflightInjector(preflightCheckModules);
final PreflightCheckService preflightCheckService = preflightInjector.getInstance(PreflightCheckService.class);
preflightCheckService.runChecks();
}
private void runPreflightWeb(List preflightCheckModules) {
List modules = new ArrayList<>(preflightCheckModules);
modules.add(new DataNodeProvisioningBindings());
modules.add(new PreflightWebModule(configuration));
modules.add(new ObjectMapperModule(chainingClassLoader));
modules.add(new SchedulerBindings());
modules.add((binder) -> binder.bind(ChainingClassLoader.class).toInstance(chainingClassLoader));
final Injector preflightInjector = getPreflightInjector(modules);
GuiceInjectorHolder.setInjector(preflightInjector);
try {
doRunWithPreflightInjector(preflightInjector);
} finally {
GuiceInjectorHolder.resetInjector();
}
}
private void doRunWithPreflightInjector(Injector preflightInjector) {
final PreflightBoot preflightBoot = preflightInjector.getInstance(PreflightBoot.class);
if (!preflightBoot.shouldRunPreflightWeb()) {
return;
}
LOG.info("Fresh installation detected, starting configuration webserver");
try {
if (configuration.isLeader() && configuration.runMigrations()) {
runMigrations(preflightInjector, MigrationType.PREFLIGHT);
}
} catch (Exception e) {
LOG.error("Exception while running migrations", e);
System.exit(1);
}
final ServiceManager serviceManager = preflightInjector.getInstance(ServiceManager.class);
final LeaderElectionService leaderElectionService = preflightInjector.getInstance(LeaderElectionService.class);
try {
leaderElectionService.startAsync().awaitRunning();
serviceManager.startAsync().awaitHealthy();
// wait till the marker document appears
while (preflightBoot.shouldRunPreflightWeb()) {
try {
LOG.debug("Preflight config still in progress, waiting for the marker document");
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
} finally {
// check, if this is the leader before shutting down preflight in case we want to delay startup
final var isLeader = leaderElectionService.isLeader();
serviceManager.stopAsync().awaitStopped();
leaderElectionService.stopAsync().awaitTerminated();
try {
// delay startup if we're not the leader to give the leader node a headstart on resume
// so it can take care of mongo-collections etc.
// and we prevent problems that occured if all nodes started exactly at the same time
if (!isLeader) {
Thread.sleep(5000);
}
} catch (InterruptedException e) {
LOG.warn("Tried to wait for a bit before resuming but got interrupted. Resuming anyway now. Error was: {}", e.getMessage());
}
}
}
private void runMongoPreflightCheck() {
// The MongoDBPreflightCheck is not run via the PreflightCheckService,
// because it also detects whether we are running on a fresh Graylog installation
final Injector injector = getMongoPreFlightInjector();
final MongoDBPreflightCheck mongoDBPreflightCheck = injector.getInstance(MongoDBPreflightCheck.class);
try {
mongoDBPreflightCheck.runCheck();
} catch (PreflightCheckException e) {
LOG.error("Preflight check failed with error: {}", e.getLocalizedMessage());
throw e;
}
if (mongoDBPreflightCheck.isFreshInstallation()) {
registerFreshInstallation();
}
}
private Injector getMongoPreFlightInjector() {
return Guice.createInjector(
new IsDevelopmentBindings(),
new NamedConfigParametersModule(jadConfig.getConfigurationBeans()),
new ConfigurationModule(configuration)
);
}
private Injector getPreflightInjector(List preflightCheckModules) {
return Guice.createInjector(
new IsDevelopmentBindings(),
new NamedConfigParametersModule(jadConfig.getConfigurationBeans()),
new ServerStatusBindings(capabilities()),
new ConfigurationModule(configuration),
new SystemStatsModule(configuration.isDisableNativeSystemStatsCollector()),
new IndexerDiscoveryModule(),
new ServerPreflightChecksModule(),
binder -> preflightCheckModules.forEach(binder::install));
}
private void setNettyNativeDefaults(PathConfiguration pathConfiguration) {
// Give netty a better spot than /tmp to unpack its tcnative libraries
if (System.getProperty("io.netty.native.workdir") == null) {
System.setProperty("io.netty.native.workdir", pathConfiguration.getNativeLibDir().toAbsolutePath().toString());
}
// Don't delete the native lib after unpacking, as this confuses needrestart(1) on some distributions
if (System.getProperty("io.netty.native.deleteLibAfterLoading") == null) {
System.setProperty("io.netty.native.deleteLibAfterLoading", "false");
}
}
@Override
protected void startCommand() {
final AuditEventSender auditEventSender = injector.getInstance(AuditEventSender.class);
final NodeId nodeId = injector.getInstance(NodeId.class);
final String systemInformation = Tools.getSystemInformation();
final Map auditEventContext = ImmutableMap.of(
"version", version.toString(),
"java", systemInformation,
"node_id", nodeId.getNodeId()
);
auditEventSender.success(AuditActor.system(nodeId), NODE_STARTUP_INITIATE, auditEventContext);
final OS os = OS.getOs();
LOG.info("Graylog {} {} starting up", commandName, version);
LOG.info("JRE: {}", systemInformation);
LOG.info("Deployment: {}", configuration.getInstallationSource());
LOG.info("OS: {}", os.getPlatformName());
LOG.info("Arch: {}", os.getArch());
try {
if (configuration.isLeader() && configuration.runMigrations()) {
runMigrations(injector, MigrationType.STANDARD);
}
} catch (Exception e) {
LOG.error("Exception while running migrations", e);
System.exit(1);
}
final ServerStatus serverStatus = injector.getInstance(ServerStatus.class);
serverStatus.initialize();
startNodeRegistration(injector);
final ActivityWriter activityWriter;
final ServiceManager serviceManager;
final Service leaderElectionService;
try {
activityWriter = injector.getInstance(ActivityWriter.class);
serviceManager = injector.getInstance(ServiceManager.class);
leaderElectionService = injector.getInstance(LeaderElectionService.class);
} catch (ProvisionException e) {
LOG.error("Guice error", e);
annotateProvisionException(e);
auditEventSender.failure(AuditActor.system(nodeId), NODE_STARTUP_INITIATE, auditEventContext);
System.exit(-1);
return;
} catch (Exception e) {
LOG.error("Unexpected exception", e);
auditEventSender.failure(AuditActor.system(nodeId), NODE_STARTUP_INITIATE, auditEventContext);
System.exit(-1);
return;
}
Runtime.getRuntime().addShutdownHook(new Thread(injector.getInstance(shutdownHook())));
// propagate default size to input plugins
MessageInput.setDefaultRecvBufferSize(configuration.getUdpRecvBufferSizes());
// Start services.
final ServiceManagerListener serviceManagerListener = injector.getInstance(ServiceManagerListener.class);
serviceManager.addListener(serviceManagerListener, MoreExecutors.directExecutor());
try {
leaderElectionService.startAsync().awaitRunning();
serviceManager.startAsync().awaitHealthy();
} catch (Exception e) {
try {
serviceManager.stopAsync().awaitStopped(configuration.getShutdownTimeout(), TimeUnit.MILLISECONDS);
} catch (TimeoutException timeoutException) {
LOG.error("Unable to shutdown properly on time. {}", serviceManager.servicesByState());
}
LOG.error("Graylog startup failed. Exiting. Exception was:", e);
auditEventSender.failure(AuditActor.system(nodeId), NODE_STARTUP_INITIATE, auditEventContext);
System.exit(-1);
}
LOG.info("Services started, startup times in ms: {}", serviceManager.startupTimes());
activityWriter.write(new Activity("Started up.", Main.class));
LOG.info("Graylog {} up and running.", commandName);
auditEventSender.success(AuditActor.system(nodeId), NODE_STARTUP_COMPLETE, auditEventContext);
// Block forever.
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
return;
}
}
public void runMigrations(Injector injector, MigrationType migrationType) {
//noinspection unchecked
final TypeLiteral> typeLiteral = (TypeLiteral>) TypeLiteral.get(Types.setOf(Migration.class));
Set migrations = injector.getInstance(Key.get(typeLiteral));
LOG.info("Running {} migrations...", migrations.size());
ImmutableSortedSet.copyOf(migrations).stream().filter(m -> m.migrationType() == migrationType).forEach(m -> {
LOG.debug("Running migration <{}>", m.getClass().getCanonicalName());
try {
m.upgrade();
} catch (Exception e) {
if (configuration.ignoreMigrationFailures()) {
LOG.warn("Ignoring failure of migration <{}>: {}", m.getClass().getCanonicalName(), e.getMessage());
} else {
throw e;
}
}
});
}
protected void savePidFile(final String pidFile) {
final String pid = Tools.getPID();
final Path pidFilePath = Paths.get(pidFile);
pidFilePath.toFile().deleteOnExit();
try {
if (isNullOrEmpty(pid) || "unknown".equals(pid)) {
throw new Exception("Could not determine PID.");
}
Files.write(pidFilePath, pid.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW, LinkOption.NOFOLLOW_LINKS);
} catch (Exception e) {
LOG.error("Could not write PID file: " + e.getMessage(), e);
System.exit(1);
}
}
@Override
protected List getSharedBindingsModules() {
final List result = super.getSharedBindingsModules();
result.add(new FreshInstallDetectionModule(isFreshInstallation()));
result.add(new GenericBindings(isMigrationCommand()));
result.add(new SecurityBindings());
result.add(new ServerStatusBindings(capabilities()));
result.add(new ValidatorModule());
result.add(new SharedPeriodicalBindings());
result.add(new SchedulerBindings());
result.add(new GenericInitializerBindings());
result.add(new SystemStatsModule(configuration.isDisableNativeSystemStatsCollector()));
result.add(new IndexerDiscoveryModule());
result.add(new CertificateRenewalBindings());
result.add(new DataNodeProvisioningBindings());
return result;
}
protected void annotateProvisionException(ProvisionException e) {
annotateInjectorExceptions(e.getErrorMessages());
throw e;
}
protected abstract Class extends Runnable> shutdownHook();
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy