package io.eguan.main;
/*
* #%L
* Project eguan
* %%
* Copyright (C) 2012 - 2017 Oodrive
* %%
* 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.
* #L%
*/
import io.eguan.configuration.MetaConfiguration;
import io.eguan.rest.container.JettyConfigurationContext;
import io.eguan.rest.container.ServerAddressConfigKey;
import io.eguan.rest.container.ServerPortConfigKey;
import io.eguan.utils.LogUtils;
import io.eguan.utils.RunCmdErrorException;
import io.eguan.utils.RunCmdUtils;
import io.eguan.utils.RunCmdUtils.RunningCmd;
import io.eguan.vold.Vold;
import io.eguan.vold.adm.RestLauncher;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.concurrent.GuardedBy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class Eguan {
static final Logger LOGGER = LoggerFactory.getLogger(Eguan.class);
/**
* Eguan shutdown hook.
*
*/
class EguanShutdownHook implements Runnable {
@Override
public final void run() {
cancelled.set(true);
// Stop cinder process
stopCinder();
// Stop REST server
stopRestServer();
// Stop VOLD
stopVold();
}
}
/** No termination */
private static final int EGUAN_OK = -1;
/** Eguan already cancelled */
private static final int EGUAN_CANCELLED = -2;
/** Normal Termination */
private static final int EXIT_END = 0;
/** Invalid usage */
private static final int EXIT_USAGE = 1;
/** Config failed */
private static final int EXIT_CONFIG_VOLD_FAILED = 2;
private static final int EXIT_CONFIG_REST_FAILED = 3;
private static final int EXIT_CONFIG_CINDER_FAILED = 4;
/** Init failed */
private static final int EXIT_INIT_VOLD_FAILED = 5;
private static final int EXIT_INIT_REST_FAILED = 6;
/** Start failed */
private static final int EXIT_VOLD_START_FAILED = 7;
private static final int EXIT_REST_START_FAILED = 8;
private static final int EXIT_CONNECT_REST_FAILED = 9;
/** Mark the eguan as cancelled */
private final AtomicBoolean cancelled = new AtomicBoolean(false);
@GuardedBy(value = "cancelled")
private Vold vold;
@GuardedBy(value = "cancelled")
private RestLauncher restLauncher;
@GuardedBy(value = "cancelled")
private RunningCmd cinder;
private static final long MAX_ACCEPTABLE_DURATION = 30 * 1000; // 30s
private static final int MAX_COUNT_WITH_NONACCEPTABLE_DURATION = 10;
private static final int MAX_RETRY_SERVER_CONNECTION = 18; // 10s*18 = 3 min
private static final int WAIT_BETWEEN_CONNECTION = 10000; // 10s
/**
* Context to load as default from internal resources.
*/
private static final String DEFAULT_CONTEXT_RESOURCE = "/vold-adm.xml";
/**
*
* @param args
*/
public static void main(final String[] args) {
// Get uncaught exceptions
Thread.setDefaultUncaughtExceptionHandler(new EguanUncaughtExceptionHandler());
// Configure SysLog appender for logback
LogUtils.initSysLog();
final Eguan eguan = new Eguan();
if (args.length < 2) {
LOGGER.error("Bad number of arguments");
System.exit(EXIT_USAGE);
}
// Check vold directory
final File voldDir = new File(args[0]);
if (!voldDir.isDirectory()) {
LOGGER.error(args[0] + " is not a directory");
System.exit(EXIT_CONFIG_VOLD_FAILED);
}
// Run an extra command?
final String[] cinderCmd;
if (args.length >= 3) {
cinderCmd = new String[] { args[2] };
{// Check command exists and is executable
final File cinderCmdFile = new File(cinderCmd[0]);
if (!cinderCmdFile.canExecute()) {
LOGGER.error("Cinder path is not an executable cmd");
System.exit(EXIT_CONFIG_CINDER_FAILED);
}
}
}
else {
cinderCmd = null;
}
// Shutdown hook
final Thread hook = new Thread(eguan.new EguanShutdownHook(), "Eguan shutdown hook");
Runtime.getRuntime().addShutdownHook(hook);
// Start VOLD
int status = eguan.startVold(voldDir);
if (status == EGUAN_CANCELLED) {
return;
}
else if (status != EGUAN_OK) {
System.exit(status);
}
// Start REST
status = eguan.startRestServer(args[1]);
if (status == EGUAN_CANCELLED) {
return;
}
else if (status != EGUAN_OK) {
System.exit(status);
}
if (cinderCmd == null) {
// No extra command, wait for end of the rest launcher
status = eguan.waitRestServer();
}
else {
// Start Cinder
status = eguan.startCinder(cinderCmd);
}
if (status != EGUAN_CANCELLED) {
System.exit(status);
}
}
/**
* Create, init and start a vold instance.
*
* @param voldDir
* the vold directory
* @return the status of the start
*/
private final int startVold(final File voldDir) {
final Vold voldTemp;
synchronized (cancelled) {
if (cancelled.get()) {
return EGUAN_CANCELLED;
}
vold = new Vold(voldDir);
assert vold != null;
voldTemp = vold;
}
try {
voldTemp.init(false);
try {
voldTemp.start();
}
catch (final Throwable t) {
LOGGER.error("Failed to start vold", t);
return EXIT_VOLD_START_FAILED;
}
}
catch (final Throwable t) {
LOGGER.error("Failed to initialize vold", t);
return EXIT_INIT_VOLD_FAILED;
}
return EGUAN_OK;
}
/**
* Stop and fini a vold instance.
*
*/
private final void stopVold() {
final Vold voldTemp;
synchronized (cancelled) {
voldTemp = vold;
vold = null;
}
if (voldTemp != null) {
LOGGER.info("VOLD shutdown requested");
try {
voldTemp.stop();
}
catch (final Throwable t) {
LOGGER.warn("Error during VOLD stop", t);
}
try {
voldTemp.fini();
}
catch (final Throwable t) {
LOGGER.warn("Error during VOLD shutdown", t);
}
}
LOGGER.info("VOLD shutdown completed");
}
/**
* Create, init and start a REST server.
*
* @param configPath
* the path which contains the config file for jetty.
*
* @return the status of the start
*/
private final int startRestServer(final String configPath) {
if (cancelled.get()) {
return EGUAN_CANCELLED;
}
try (final FileInputStream configInputStream = new FileInputStream(configPath);
final InputStream contextInputStream = Eguan.class.getResourceAsStream(DEFAULT_CONTEXT_RESOURCE);
final FileInputStream propertiesInputStream = new FileInputStream(configPath)) {
final RestLauncher restLauncherTemp;
synchronized (cancelled) {
restLauncher = new RestLauncher(configInputStream, contextInputStream);
restLauncherTemp = restLauncher;
}
try {
restLauncherTemp.init();
try {
restLauncherTemp.start();
// Try to connect to the REST server
int count = MAX_RETRY_SERVER_CONNECTION;
boolean succeed = false;
final SocketChannel socketChannel = SocketChannel.open();
try {
final MetaConfiguration configuration = MetaConfiguration.newConfiguration(
propertiesInputStream, JettyConfigurationContext.getInstance());
InetAddress serverAddress = ServerAddressConfigKey.getInstance().getTypedValue(configuration);
final int serverPort = ServerPortConfigKey.getInstance().getTypedValue(configuration);
while (count-- != 0) {
try {
if (serverAddress.isAnyLocalAddress()) {
serverAddress = InetAddress.getLoopbackAddress();
}
succeed = socketChannel.connect(new InetSocketAddress(serverAddress, serverPort));
break;
}
catch (final Throwable t) {
// Ignore and retry
LOGGER.error("Failed to connect REST server");
}
Thread.sleep(WAIT_BETWEEN_CONNECTION);
}
}
finally {
socketChannel.close();
}
if (!succeed) {
LOGGER.error("Failed to start rest");
return EXIT_CONNECT_REST_FAILED;
}
}
catch (final Throwable t) {
LOGGER.error("Failed to start rest", t);
return EXIT_REST_START_FAILED;
}
}
catch (final Throwable t) {
LOGGER.error("Failed to init rest", t);
return EXIT_INIT_REST_FAILED;
}
}
catch (final Throwable t) {
LOGGER.error("Failed to init rest", t);
return EXIT_CONFIG_REST_FAILED;
}
return EGUAN_OK;
}
/**
* Stop the instance of the REST server.
*
*/
private final void stopRestServer() {
final RestLauncher restLauncherTemp;
synchronized (cancelled) {
restLauncherTemp = restLauncher;
restLauncher = null;
}
if (restLauncherTemp != null) {
LOGGER.info("Rest Launcher shutdown requested");
try {
restLauncherTemp.stop();
}
catch (final Throwable t) {
LOGGER.warn("Error during VOLD stop", t);
}
try {
restLauncherTemp.fini();
}
catch (final Throwable t) {
LOGGER.warn("Error during VOLD shutdown", t);
}
}
LOGGER.info("Rest Launcher shutdown completed");
}
/**
* Wait the end of the REST server.
*
* @return the status
*/
private final int waitRestServer() {
try {
final RestLauncher restLauncherTemp;
synchronized (cancelled) {
if (cancelled.get()) {
return EGUAN_CANCELLED;
}
assert restLauncher != null;
restLauncherTemp = restLauncher;
}
restLauncherTemp.join();
}
catch (final Throwable t) {
LOGGER.error("Interrupted", t);
}
return EXIT_END;
}
/**
* Create and start cinder.
*
* @param cinderCmd
* the command used to launch cinder
* @return the status
*/
private final int startCinder(final String[] cinderCmd) {
synchronized (cancelled) {
if (cancelled.get()) {
return EGUAN_CANCELLED;
}
cinder = RunCmdUtils.newRunningCmd(cinderCmd, Eguan.class);
}
return waitCinder();
}
/**
* Stop the process cinder.
*/
private final void stopCinder() {
final RunningCmd cinderTmp;
synchronized (cancelled) {
cinderTmp = cinder;
cinder = null;
}
if (cinderTmp != null) {
LOGGER.info("Cinder shutdown requested");
cinderTmp.kill();
}
LOGGER.info("Cinder shutdown completed");
}
/**
* Wait the end and monitor cinder process.
*
* @return the status
*/
private final int waitCinder() {
int count = 0;
long startTime = 0;
int exitValue = EXIT_END;
while (count < MAX_COUNT_WITH_NONACCEPTABLE_DURATION) {
final RunningCmd cinderTemp;
synchronized (cancelled) {
if (cancelled.get()) {
return EGUAN_CANCELLED;
}
assert cinder != null;
cinderTemp = cinder;
}
try {
startTime = System.currentTimeMillis();
cinderTemp.run();
}
catch (final RunCmdErrorException e) {
LOGGER.error("Failed to start cinder", e);
exitValue = e.getExitValue();
}
catch (final Throwable t) {
LOGGER.error("Failed to start cinder", t);
}
// cinder is not running
final long duration = System.currentTimeMillis() - startTime;
if (duration < MAX_ACCEPTABLE_DURATION) {
count++;
}
else {
count = 0;
}
}
LOGGER.error("End of Cinder");
// Return the last exit value
return exitValue;
}
}