All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.lithium.flow.runner.RunnerMain Maven / Gradle / Ivy

/*
 * Copyright 2017 Lithium Technologies, Inc.
 *
 * 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.lithium.flow.runner;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.singletonList;

import com.lithium.flow.config.Config;
import com.lithium.flow.config.ConfigBuilder;
import com.lithium.flow.config.ConfigLoader;
import com.lithium.flow.config.Configs;
import com.lithium.flow.config.loaders.FileConfigLoader;
import com.lithium.flow.filer.Filer;
import com.lithium.flow.filer.RecordPath;
import com.lithium.flow.io.Swallower;
import com.lithium.flow.shell.Exec;
import com.lithium.flow.shell.Shell;
import com.lithium.flow.util.HostUtils;
import com.lithium.flow.util.Logs;
import com.lithium.flow.util.Main;
import com.lithium.flow.util.Needle;
import com.lithium.flow.util.Sleep;
import com.lithium.flow.util.Threader;
import com.lithium.flow.util.Unchecked;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;

import com.google.common.base.Joiner;
import com.google.common.base.Splitter;

/**
 * @author Matt Ayres
 */
public class RunnerMain {
	private static final Logger log = Logs.getLogger();

	private final Config runnerConfig;
	private final Filer destFiler;
	private final VaultRun vaultRun;
	private final RunnerContext context;
	private final String host;
	private Needle runNeedle;
	private Exec runExec;

	public RunnerMain(@Nonnull Config config, @Nonnull Config deployConfig, @Nonnull RunnerContext context)
			throws IOException {
		checkNotNull(config);
		checkNotNull(deployConfig);
		this.context = checkNotNull(context);

		runnerConfig = config.toBuilder().addAll(subConfig(deployConfig, "runner")).build();

		String name = runnerConfig.getString("name");
		host = runnerConfig.getString("host");
		String user = context.getAccess().getLogin(host).getUser();
		log.info("deploying {} to {}@{}", name, user, host);

		vaultRun = new VaultRun(context.getVault(), context.getAccess().getPrompt(), runnerConfig);

		destFiler = new FasterShellFiler(getShell().getFiler(), this::getShell);

		if (runnerConfig.getBoolean("kill.only", false)) {
			kill();
			return;
		}

		RunnerSync sync = new RunnerSync(runnerConfig, context, destFiler);
		sync.sync();

		kill();

		deploy(deployConfig);
	}

	@Nonnull
	private Shell getShell() throws IOException {
		return context.getShore().getShell(host);
	}

	public void deploy(@Nonnull Config deployConfig) throws IOException {
		vaultRun.deploy(destFiler);

		String configOut = runnerConfig.getString("config.out");
		destFiler.createDirs(RecordPath.getFolder(configOut));
		writeConfig(deployConfig, destFiler.writeFile(configOut));
		log.debug("wrote: {}", configOut);

		String classpath = Joiner.on(":").join(context.getClasspath(runnerConfig.getString("dest.dir")));
		log.debug("classpath: {}", classpath);

		installJava();

		destFiler.close();

		context.getHostsMeasure().incDone();

		if (!runnerConfig.getBoolean("run", true)) {
			return;
		}

		String prefix = StringUtils.rightPad(runnerConfig.getString("name"), 15, '.') + " ";
		run(prefix, classpath, vaultRun.getEnv());
	}

	private void run(@Nonnull String prefix, @Nonnull String classpath, @Nullable String env) {
		try {
			runNeedle = context.getRunThreader().needle();

			List commands = new ArrayList<>();
			commands.add("export CLASSPATH=" + classpath);
			if (env != null) {
				commands.add(env);
			}
			commands.add("cd " + runnerConfig.getString("dest.dir"));
			for (String link : runnerConfig.getList("links", Configs.emptyList())) {
				Iterator it = Splitter.on(':').split(link).iterator();
				String target = it.next();
				String linkName = it.next();
				commands.add("rm -f " + linkName);
				commands.add("ln -sf " + target + " " + linkName);
			}

			String command = runnerConfig.getString("java.command");
			if (runnerConfig.getTime("relay", "0") > 0) {
				commands.add("export RELAY_COMMAND='" + command + "'");
				commands.add(runnerConfig.getString("relay.command"));
			} else {
				commands.add(command);
			}

			String readyString = runnerConfig.getString("run.readyString", null);
			AtomicBoolean ready = new AtomicBoolean();

			commands.forEach(run -> log.debug("running: {}", run));
			runExec = getShell().exec(commands);
			runNeedle.execute("out@" + host, () -> runExec.out().forEach(line -> {
				System.out.println(prefix + line);
				if (readyString != null && line.contains(readyString)) {
					ready.set(true);
				}
			}));
			runNeedle.execute("err@" + host, () -> runExec.err().forEach(line -> System.err.println(prefix + line)));

			if (readyString != null) {
				Sleep.until(ready::get);
				Sleep.softly(runnerConfig.getTime("run.readySleep", "0"));
			}
		} catch (IOException e) {
			log.warn("exec failed", e);
		}
	}

	private void finish() {
		if (runNeedle != null) {
			runNeedle.finish();
			Swallower.close(runExec);
		}
		log.debug("finished");
	}

	private void writeConfig(@Nonnull Config config, @Nonnull OutputStream out) throws IOException {
		boolean sorted = runnerConfig.getBoolean("config.sorted", true);
		Map map = sorted ? config.asSortedMap() : config.asMap();
		try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, UTF_8))) {
			for (Map.Entry entry : map.entrySet()) {
				writer.write(entry.getKey() + " = " + entry.getValue() + "\r\n");
			}
		}
	}

	private void installJava() throws IOException {
		String check = runnerConfig.getString("java.check");
		String md5 = runnerConfig.getString("java.md5");
		String install = runnerConfig.getString("java.install");

		String checkMd5 = getShell().exec(check).line();
		log.debug("java check md5: {}", checkMd5);
		if (!md5.equals(checkMd5)) {
			log.debug("java install: " + install);
			getShell().exec(install).exit();
		}
	}

	@Nullable
	private Integer getPid(@Nonnull String key) throws IOException {
		String grep = "grep \"D" + key + "=" + runnerConfig.getString("name") + " \"";
		String command = "ps auxw | " + grep + " | grep -v grep | awk '{ print $2 }'";
		log.debug("running: {}", command);
		String pid = getShell().exec(command).line();
		return pid.isEmpty() ? null : Integer.parseInt(pid);
	}

	private void killPid(@Nonnull Integer pid, boolean force) throws IOException {
		getShell().exec("kill " + (force ? "-9 " : "") + pid).exit();
	}

	private void kill() throws IOException {
		kill("relay");
		kill("name");
	}

	private void kill(@Nonnull String key) throws IOException {
		String name = runnerConfig.getString("name");
		Integer pid = getPid(key);
		if (pid == null) {
			return;
		}

		if (!runnerConfig.getBoolean("kill", false)) {
			throw new IOException(Logs.message("pid {} already exists for {}={}", pid, key, name));
		}

		log.info("killing existing pid {} for {}={}", pid, key, name);
		killPid(pid, false);

		long maxTime = runnerConfig.getTime("kill.maxTime", "10s");
		long endTime = System.currentTimeMillis() + maxTime;

		while (System.currentTimeMillis() < endTime) {
			Sleep.softly(500);
			if (getPid(key) == null) {
				log.info("killed existing pid {} for {}={}", pid, key, name);
				return;
			}
		}

		if (!runnerConfig.getBoolean("kill.force", false)) {
			throw new IOException(Logs.message("failed to kill existing pid {} for {}={}", pid, key, name));
		}

		log.info("force killing existing pid {} for {}={}", pid, key, name);
		killPid(pid, true);

		endTime = System.currentTimeMillis() + maxTime;
		while (System.currentTimeMillis() < endTime) {
			Sleep.softly(500);
			if (getPid(key) == null) {
				log.info("force killed existing pid {} for {}={}", pid, key, name);
				return;
			}
		}

		throw new IOException(Logs.message("failed to force kill existing pid {} for {}={}", pid, key, name));
	}

	@Nonnull
	private static Config subConfig(@Nonnull Config config, @Nonnull String prefix) {
		Config allowConfig = config.toBuilder().allowUndefined(true).build();
		ConfigBuilder builder = Configs.newBuilder();
		for (String key : config.getPrefixKeys(prefix)) {
			builder.setString(key.replace(prefix + ".", ""), allowConfig.getString(key));
		}
		return builder.build();
	}

	public static void main(String[] args) throws Exception {
		Config config = Main.config();
		Logs.configure(config);

		RunnerContext context = new RunnerContext(config);

		List runners = new CopyOnWriteArrayList<>();

		for (String path : config.getList("config")) {
			Threader parallel = new Threader(config.getInt("parallel", -1));

			File file = new File(path);
			ConfigLoader loader = new FileConfigLoader(file.getParent());
			Config deployConfig = Configs.newBuilder().addLoader(loader).addAll(subConfig(config, "deploy"))
					.include(file.getAbsolutePath()).build();

			if (deployConfig.containsKey("runner.hosts")) {
				List hosts = deployConfig.getList("runner.hosts", Splitter.on(' '));
				HostUtils.expand(hosts).forEach(runHost -> {
					Config runConfig = deployConfig.toBuilder().setString("runner.host", runHost).build();
					parallel.execute(runHost, () -> runners.add(new RunnerMain(config, runConfig, context)));
				});
			} else {
				List nums = deployConfig.getList("runner.nums", singletonList("0"), Splitter.on(' '));
				HostUtils.expand(nums).forEach(num -> {
					Config runConfig = deployConfig.toBuilder().setString("num", num).build();
					parallel.execute(num, () -> runners.add(new RunnerMain(config, runConfig, context)));
				});
			}

			Thread.sleep(config.getTime("sleep", "0"));
			parallel.finish();
		}

		context.getProgress().finish();

		runners.forEach(runner -> Unchecked.run(runner::finish));
		context.close();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy